This commit is contained in:
likun 2025-04-30 15:46:14 +08:00
parent e9d01aa19f
commit 29cb1e0bb8
91 changed files with 3688 additions and 609 deletions

View File

@ -0,0 +1,38 @@
package api
func GetGameApiInstance() IGameApi {
return gameApiInstance
}
var gameApiInstance IGameApi
type IGameApi interface {
GetRoutes(req *GetRoutesReq) (*GetRoutesRsp, error) // 用户服务通过用户权限拉取游戏项目路由权限,生成动态菜单
}
func RegisterGameApi(handler IGameApi) {
gameApiInstance = handler
}
type ResourceInitInfo struct {
Resource string `json:"resource"`
Desc string `json:"desc"`
ShowMethods []string `json:"show_methods"` // 资源权限列表
MethodsPermissionStr []string `json:"methods_permission"` // 资源权限字符串
}
type ProjectInitInfo struct {
ProjectId int `json:"project_id"`
ProjectName string `json:"project_name"`
ResourceList []*ResourceInitInfo `json:"resource_list"` // 权限过滤后客户端可以显示的资源操作
ResourceTotalList []*ResourceInitInfo `json:"resource_total_list"` // 所有资源操作权限,可以用来做权限分配树初始化
}
type GetRoutesReq struct {
IsAdmin bool
Permissions []string
}
type GetRoutesRsp struct {
Projects []*ProjectInitInfo
}

View File

@ -1,6 +1,7 @@
package domain
import (
"admin/apps/game/api"
"admin/apps/game/domain/entity"
"admin/apps/game/domain/projects"
"admin/apps/game/domain/repo"
@ -17,14 +18,14 @@ type CommonResourceService struct {
}
func initCommonResourcesRepo(db *gorm.DB) {
r(consts.ResourcesName_Project, "项目管理", repo.NewCommonResourceRepo(db, &model.Project{}), ShowMethod_Get|ShowMethod_Put)
r(consts.ResourcesName_Project, "项目管理", repo.NewCommonResourceRepo(db, &model.Project{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_Server, "服务器管理", repo.NewCommonResourceRepo(db, &model.Server{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_Account, "账号管理", repo.NewCommonResourceRepo(db, &model.Account{}), ShowMethod_Get) // 账号管理不需要在后台读写数据都是通过项目api拉
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|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_Notice, "公告", repo.NewCommonResourceRepo(db, &model.Notice{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_RewardCode, "奖励码", repo.NewCommonResourceRepo(db, &model.RewardCode{}), ShowMethod_Get)
r(consts.ResourcesName_DevicePush, "设备推送", repo.NewCommonResourceRepo(db, &model.DevicePush{}), ShowMethod_Get)
@ -38,16 +39,16 @@ func NewCommonResourceService(db *gorm.DB) *CommonResourceService {
return svc
}
func (svc *CommonResourceService) List(projectId string, resource string, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
projectEt, find, err := svc.projectRepo.GetByProjectId(projectId)
func (svc *CommonResourceService) List(projectId int, resource string, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return nil, nil, err
}
if !find {
if resource != consts.ResourcesName_Project && !find {
return nil, nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
fieldsDescInfo, etList, err := findCommResourceRepo(resource).List(projectId, pageNo, pageLen, extraQuery, args...)
fieldsDescInfo, etList, err := findCommResourceRepo(resource).List(projectEt, pageNo, pageLen, extraQuery, args...)
if err != nil {
return nil, nil, err
}
@ -57,15 +58,23 @@ func (svc *CommonResourceService) List(projectId string, resource string, pageNo
}
// 执行各个项目特定的钩子方法
if hook, ok := projects.GetProjectResourceHook(projectId, resource).(projects.IPostResourceOpListHook); ok {
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpListHook); ok {
return hook.List(projectEt, resource, pageNo, pageLen, fieldsDescInfo, retList, extraQuery, args...)
}
return fieldsDescInfo, retList, nil
}
func (svc *CommonResourceService) GetById(projectId string, resource string, id int) ([]*dto.CommonDtoFieldDesc, dto.CommonDtoValues, *entity.CommonResource, bool, error) {
fieldsDescInfo, et, find, err := findCommResourceRepo(resource).GetById(projectId, id)
func (svc *CommonResourceService) GetById(projectId int, resource string, id int) ([]*dto.CommonDtoFieldDesc, dto.CommonDtoValues, *entity.CommonResource, bool, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return nil, nil, nil, false, err
}
if resource != consts.ResourcesName_Project && !find {
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)
if err != nil {
return nil, nil, nil, false, err
}
@ -73,16 +82,16 @@ func (svc *CommonResourceService) GetById(projectId string, resource string, id
return fieldsDescInfo, et.ToCommonDto(), et, find, nil
}
func (svc *CommonResourceService) Create(projectId string, resource string, dtoObj dto.CommonDtoValues) (dto.CommonDtoValues, error) {
projectEt, find, err := svc.projectRepo.GetByProjectId(projectId)
func (svc *CommonResourceService) Create(projectId int, resource string, dtoObj dto.CommonDtoValues) (dto.CommonDtoValues, error) {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return nil, err
}
if !find {
if resource != consts.ResourcesName_Project && !find {
return nil, errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
et, err := findCommResourceRepo(resource).Create(projectId, dtoObj)
et, err := findCommResourceRepo(resource).Create(projectEt, dtoObj)
if err != nil {
return nil, err
}
@ -90,7 +99,7 @@ func (svc *CommonResourceService) Create(projectId string, resource string, dtoO
// 返回新的实体插入数据库之后会填入数据库id所以要返给前端刷新id数据
// 执行各个项目特定的钩子方法
if hook, ok := projects.GetProjectResourceHook(projectId, resource).(projects.IPostResourceOpCreateHook); ok {
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpCreateHook); ok {
dtoObj := et.ToCommonDto()
err = hook.Create(projectEt, resource, dtoObj)
if err != nil {
@ -102,22 +111,22 @@ func (svc *CommonResourceService) Create(projectId string, resource string, dtoO
return et.ToCommonDto(), err
}
func (svc *CommonResourceService) Edit(projectId string, resource string, dtoObj dto.CommonDtoValues) error {
projectEt, find, err := svc.projectRepo.GetByProjectId(projectId)
func (svc *CommonResourceService) Edit(projectId int, resource string, dtoObj dto.CommonDtoValues) error {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return err
}
if !find {
if resource != consts.ResourcesName_Project && !find {
return errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
err = findCommResourceRepo(resource).Edit(projectId, dtoObj)
err = findCommResourceRepo(resource).Edit(projectEt, dtoObj)
if err != nil {
return err
}
// 执行各个项目特定的钩子方法
if hook, ok := projects.GetProjectResourceHook(projectId, resource).(projects.IPostResourceOpEditHook); ok {
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpEditHook); ok {
err = hook.Edit(projectEt, resource, dtoObj)
if err != nil {
return err
@ -128,23 +137,23 @@ func (svc *CommonResourceService) Edit(projectId string, resource string, dtoObj
return nil
}
func (svc *CommonResourceService) Delete(projectId string, resource string, id int) error {
projectEt, find, err := svc.projectRepo.GetByProjectId(projectId)
func (svc *CommonResourceService) Delete(projectId int, resource string, id int) error {
_, projectEt, find, err := svc.projectRepo.GetById(projectId)
if err != nil {
return err
}
if !find {
if resource != consts.ResourcesName_Project && !find {
return errcode.New(errcode.ServerError, "not found project %v db data", projectId)
}
oldEt, find, err := findCommResourceRepo(resource).Delete(projectId, id)
oldEt, find, err := findCommResourceRepo(resource).Delete(projectEt, id)
if err != nil {
return err
}
// 执行各个项目特定的钩子方法
if find {
if hook, ok := projects.GetProjectResourceHook(projectId, resource).(projects.IPostResourceOpDeleteHook); ok {
if hook, ok := projects.GetProjectResourceHook(projectEt, resource).(projects.IPostResourceOpDeleteHook); ok {
err = hook.Delete(projectEt, resource, oldEt.ToCommonDto())
if err != nil {
return err
@ -156,10 +165,10 @@ func (svc *CommonResourceService) Delete(projectId string, resource string, id i
return nil
}
func (svc *CommonResourceService) GetSupportResourcesList() []*dto.ResourceInitInfo {
list := make([]*dto.ResourceInitInfo, 0, len(commResourcesRepo))
func (svc *CommonResourceService) GetSupportResourcesList(permissions []string) []*api.ResourceInitInfo {
list := make([]*api.ResourceInitInfo, 0, len(commResourcesRepo))
for _, v := range commResourcesRepo {
list = append(list, &dto.ResourceInitInfo{
list = append(list, &api.ResourceInitInfo{
Resource: v.Resource,
Desc: v.Desc,
ShowMethods: v.ShowMethods,
@ -187,9 +196,10 @@ var commResourcesRepo = make([]*resourceRepoInfo, 0)
func r(resource, desc string, repo repo.ICommonResourceRepo, showMethods int) {
curRepo := &resourceRepoInfo{
Resource: resource,
Desc: desc,
Repo: repo,
Resource: resource,
Desc: desc,
Repo: repo,
ShowMethods: make([]string, 0),
}
if showMethods&ShowMethod_Get == ShowMethod_Get {

View File

@ -7,8 +7,8 @@ import (
)
type CommonResource struct {
ProjectEt *Project
Po model.IModel
Project *Project // 所属项目
Po model.IModel
}
func (et *CommonResource) FromPo(po model.IModel) *CommonResource {
@ -43,7 +43,7 @@ func (et *CommonResource) ToCommonDto() dto.CommonDtoValues {
return poToCommonDtoNo(et.Po)
}
func (et *CommonResource) GetDtoFieldsDescInfo(projectId string) []*dto.CommonDtoFieldDesc {
func (et *CommonResource) GetDtoFieldsDescInfo(project *Project) []*dto.CommonDtoFieldDesc {
ptrVo := reflect.ValueOf(et.Po)
@ -57,10 +57,12 @@ func (et *CommonResource) GetDtoFieldsDescInfo(projectId string) []*dto.CommonDt
//fo := vo.Field(i)
if ft.Name == "ProjectId" {
continue
if _, ok := et.Po.(*model.Project); !ok {
continue
}
}
f1 := getFieldTypeDtoDescInfo(projectId, ptrVo, ft)
f1 := getFieldTypeDtoDescInfo(project, ptrVo, ft)
obj = append(obj, f1)
}

View File

@ -1,24 +1,34 @@
package entity
import "admin/apps/game/model"
import (
"admin/apps/game/model"
"admin/apps/game/model/dto"
)
type Project struct {
*CommonResource
ProjectPo *model.Project
Po *model.Project
}
func (commEt *CommonResource) ToProjectEntity() *Project {
po := commEt.ToPo().(*model.Project)
return &Project{
CommonResource: commEt,
ProjectPo: po,
func FromProjectPo(po *model.Project) *Project {
return &Project{Po: po}
}
func (project *Project) FromDto(obj dto.CommonDtoValues) *Project {
project.ToCommonResource().FromDto(obj)
return project
}
func (project *Project) ToCommonResource() *CommonResource {
er := &CommonResource{
Project: project,
}
return er.FromPo(project.Po)
}
func (project *Project) GetProjectPo() *model.Project {
return project.ProjectPo
return project.Po
}
func (project *Project) GetApiAddr() string {
return project.ProjectPo.ApiAddr
return project.Po.ApiAddr
}

View File

@ -30,14 +30,14 @@ func poToCommonDtoNo(po any) dto.CommonDtoValues {
obj[ft.Name] = fo.Interface()
if ft.Type.Name() == "Time" && ft.Type.PkgPath() == "time" {
obj[ft.Name] = fo.Interface().(time.Time).Format(time.DateTime)
obj[ft.Name] = fo.Interface().(time.Time).Format("2006/01/02 15:04:05")
}
}
return obj
}
func getFieldTypeDtoDescInfo(projectId string, poValue reflect.Value, fieldType reflect.StructField) *dto.CommonDtoFieldDesc {
func getFieldTypeDtoDescInfo(project *Project, poValue reflect.Value, fieldType reflect.StructField) *dto.CommonDtoFieldDesc {
name := fieldType.Tag.Get("name")
if name == "" {
name = fieldType.Name
@ -68,7 +68,7 @@ func getFieldTypeDtoDescInfo(projectId string, poValue reflect.Value, fieldType
if !method.IsValid() {
xlog.Warnf("po %v choices %v function not found in model", poValue.Type().Name(), cf)
} else {
rets := method.Call([]reflect.Value{reflect.ValueOf(projectId)})
rets := method.Call([]reflect.Value{reflect.ValueOf(project.Po)})
f1.Choices = rets[0].Interface().([]*dto.CommonDtoFieldChoice)
}

View File

@ -2,12 +2,9 @@ package domain
import (
"admin/apps/game/domain/entity"
"admin/apps/game/domain/projects"
"admin/apps/game/domain/repo"
"admin/apps/game/model"
"admin/lib/xlog"
"admin/apps/game/model/dto"
"gorm.io/gorm"
"time"
)
type ProjectService struct {
@ -19,53 +16,55 @@ func NewProjectService(db *gorm.DB) *ProjectService {
return &ProjectService{repo: repo.NewProjectRepo(db)}
}
func (svc *ProjectService) List() ([]*entity.Project, error) {
return svc.repo.List()
func (svc *ProjectService) List() ([]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, []*entity.Project, error) {
fields, rows, err := svc.repo.List()
if err != nil {
return nil, nil, nil, err
}
commonList := make([]dto.CommonDtoValues, 0, len(rows))
for _, v := range rows {
commonList = append(commonList, v.ToCommonResource().ToCommonDto())
}
return fields, commonList, rows, nil
}
func (svc *ProjectService) EnsureProjectsDBData() error {
_, err := svc.repo.EnsureProjectsDBData()
go svc.startProjectBackGroundWorker()
return err
func (svc *ProjectService) GetById(id int) ([]*dto.CommonDtoFieldDesc, dto.CommonDtoValues, *entity.CommonResource, bool, error) {
fields, et, find, err := svc.repo.GetById(id)
if err != nil {
return nil, nil, nil, false, err
}
cr := et.ToCommonResource()
return fields, cr.ToCommonDto(), cr, find, nil
}
func (svc *ProjectService) GetProjectInvokeApiAddr(projectId string, serverIds []int) ([]string, error) {
et, _, err := svc.repo.GetByProjectId(projectId)
func (svc *ProjectService) Create(dtoObj dto.CommonDtoValues) (dto.CommonDtoValues, error) {
et, err := svc.repo.Create(dtoObj)
if err != nil {
return nil, err
}
return []string{et.ToPo().(*model.Project).ApiAddr}, nil
return et.ToCommonResource().ToCommonDto(), nil
}
func (svc *ProjectService) startProjectBackGroundWorker() {
for {
allProjects, err := svc.repo.List()
if err != nil {
xlog.Warnf("list all projects error:%v", err)
time.Sleep(time.Second * 60)
continue
}
for _, p := range allProjects {
apiAddr := p.GetApiAddr()
if apiAddr == "" {
continue
}
po := p.ToPo().(*model.Project)
handler := projects.GetProjectValueChoicesGetHook(po.ProjectId)
if handler != nil {
itemsChoices, err := handler.GetItems(p)
if err != nil {
xlog.Warnf("get project %v items error:%v", po.ProjectId, err)
} else {
if len(itemsChoices) > 100 {
itemsChoices = itemsChoices[:100]
}
model.ProjectsAllItems[po.ProjectId] = itemsChoices
}
}
}
time.Sleep(time.Second * 60)
func (svc *ProjectService) Edit(dtoObj dto.CommonDtoValues) error {
err := svc.repo.Edit(dtoObj)
if err != nil {
return err
}
return nil
}
func (svc *ProjectService) Delete(id int) error {
_, find, err := svc.repo.Delete(id)
if find {
}
return err
}
func (svc *ProjectService) GetProjectInvokeApiAddr(projectId int, serverIds []int) ([]string, error) {
_, et, _, err := svc.repo.GetById(projectId)
if err != nil {
return nil, err
}
return []string{et.Po.ApiAddr}, nil
}

View File

@ -1,6 +1,7 @@
package projects
import (
"admin/apps/game/domain/entity"
"admin/apps/game/domain/projects/smdl"
"admin/internal/consts"
)
@ -23,12 +24,12 @@ var projectsValueChoicesGetHook = map[string]IGetAllValueChoicesHook{
consts.RegisteredProjectId_shenmodalu: &smdl.Items{},
}
func GetProjectResourceHook(projectId, resource string) any {
project, find := projectsResourceHookMgr[projectId]
func GetProjectResourceHook(project *entity.Project, resource string) any {
projectResourceHooks, find := projectsResourceHookMgr[project.Po.ProjectType]
if !find {
return nil
}
return project[resource]
return projectResourceHooks[resource]
}
func GetProjectValueChoicesGetHook(projectId string) IGetAllValueChoicesHook {

View File

@ -18,7 +18,7 @@ func (hook *AccountHook) List(projectInfo *entity.Project, resource string, page
[]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return nil, nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return nil, nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
type RspData struct {

View File

@ -4,9 +4,10 @@ import (
"admin/apps/game/domain/entity"
"admin/apps/game/model"
"admin/apps/game/model/dto"
"admin/internal/consts"
"admin/internal/errcode"
"admin/lib/httpclient"
"fmt"
"admin/lib/xlog"
"net/url"
"strconv"
"time"
@ -18,12 +19,25 @@ type BanHook struct {
func (hook *BanHook) Create(projectInfo *entity.Project, resource string, dtoObj dto.CommonDtoValues) error {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
et := (&entity.CommonResource{}).FromPo(&model.Ban{}).FromDto(dtoObj)
banInfo := et.ToPo().(*model.Ban)
banApi := ""
switch banInfo.BanType {
case consts.BanType_Role:
banApi = "banrole"
case consts.BanType_RoleChat:
banApi = "banroletalk"
default:
xlog.Warnf("神魔大陆不支持此类型的封禁:%v", banInfo.BanType)
return nil
}
roleId := banInfo.Value
params := &url.Values{}
params.Add("server", banInfo.ServerConfID)
params.Add("roleid", roleId)
@ -33,22 +47,22 @@ func (hook *BanHook) Create(projectInfo *entity.Project, resource string, dtoObj
if expireAt <= 0 {
// 解封
params.Add("forbidtime", "0")
params.Add("desc", "待写原因")
params.Add("desc", "你被永久封禁了")
params.Add("desc", banInfo.BanReason)
params.Add("notifytouser", banInfo.BanNotifyReason)
} else {
dura := expireAt - time.Now().Unix()
dura := (expireAt - time.Now().Unix()) / 60 // 神魔大陆封禁是分钟
if dura <= 0 {
// 解封
params.Add("forbidtime", "0")
} else {
params.Add("forbidtime", strconv.FormatInt(dura, 10))
params.Add("desc", "待写原因")
params.Add("desc", fmt.Sprintf("你被封禁了,封禁到期时间:%v", banInfo.ExpireAt))
params.Add("desc", banInfo.BanReason)
params.Add("notifytouser", banInfo.BanNotifyReason)
}
}
rsp := make(map[string]any)
err := httpclient.Request(alisrvAddr+"/banrole", "get", params, &rsp)
err := httpclient.Request(alisrvAddr+"/"+banApi, "get", params, &rsp)
if err != nil {
return err
}
@ -59,23 +73,35 @@ func (hook *BanHook) Create(projectInfo *entity.Project, resource string, dtoObj
func (hook *BanHook) Delete(projectInfo *entity.Project, resource string, dtoObj dto.CommonDtoValues) error {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
et := (&entity.CommonResource{}).FromPo(&model.Ban{}).FromDto(dtoObj)
banInfo := et.ToPo().(*model.Ban)
banApi := ""
switch banInfo.BanType {
case consts.BanType_Role:
banApi = "banrole"
case consts.BanType_RoleChat:
banApi = "banroletalkdel"
default:
xlog.Warnf("神魔大陆不支持此类型的封禁:%v", banInfo.BanType)
return nil
}
roleId := banInfo.Value
params := &url.Values{}
params.Add("server", banInfo.ServerConfID)
params.Add("roleid", roleId)
// 解封
params.Add("forbidtime", "0")
params.Add("forbidtime", "-1")
params.Add("desc", "")
params.Add("notifytouser", "")
rsp := make(map[string]any)
err := httpclient.Request(alisrvAddr+"/banrole", "get", params, &rsp)
err := httpclient.Request(alisrvAddr+"/"+banApi, "get", params, &rsp)
if err != nil {
return err
}

View File

@ -13,7 +13,7 @@ type Items struct {
func (items *Items) GetItems(projectInfo *entity.Project) ([]*dto.CommonDtoFieldChoice, error) {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
type RspData struct {

View File

@ -19,7 +19,7 @@ type MailGlobalHook struct {
func (hook *MailGlobalHook) Create(projectInfo *entity.Project, resource string, dtoObj dto.CommonDtoValues) error {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
et := (&entity.CommonResource{}).FromPo(&model.GlobalMail{}).FromDto(dtoObj)
@ -48,10 +48,10 @@ func (hook *MailGlobalHook) Create(projectInfo *entity.Project, resource string,
switch {
case serverIdsLen == 0:
// 为空就发给所有服
serverList, err := repo.ServerRepoInstance.List(projectInfo.ProjectPo.ProjectId)
serverList, err := repo.ServerRepoInstance.List(projectInfo.Po.ID)
if err != nil {
xlog.Errorf("list all server by project:%v error:%v", projectInfo.ProjectPo.ProjectId, err)
return errcode.New(errcode.ServerError, "list all server by project:%v error:%v", projectInfo.ProjectPo.ProjectId, err)
xlog.Errorf("list all server by project:%v error:%v", projectInfo.Po.ID, err)
return errcode.New(errcode.ServerError, "list all server by project:%v error:%v", projectInfo.Po.ID, err)
}
for _, v := range serverList {
serverIds = append(serverIds, v.ToPo().(*model.Server).ServerConfID)
@ -93,7 +93,7 @@ func (hook *MailGlobalHook) Create(projectInfo *entity.Project, resource string,
func (hook *MailGlobalHook) Delete(projectInfo *entity.Project, resource string, dtoObj dto.CommonDtoValues) error {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
et := (&entity.CommonResource{}).FromPo(&model.GlobalMail{}).FromDto(dtoObj)

View File

@ -18,7 +18,7 @@ type MailRoleHook struct {
func (hook *MailRoleHook) Create(projectInfo *entity.Project, resource string, dtoObj dto.CommonDtoValues) error {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
et := (&entity.CommonResource{}).FromPo(&model.RoleMail{}).FromDto(dtoObj)
@ -65,7 +65,7 @@ func (hook *MailRoleHook) Create(projectInfo *entity.Project, resource string, d
func (hook *MailRoleHook) Delete(projectInfo *entity.Project, resource string, dtoObj dto.CommonDtoValues) error {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
et := (&entity.CommonResource{}).FromPo(&model.RoleMail{}).FromDto(dtoObj)

View File

@ -28,7 +28,7 @@ func (hook *RoleHook) List(projectInfo *entity.Project, resource string, pageNo,
[]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return nil, nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return nil, nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
type RspData struct {

View File

@ -15,7 +15,7 @@ func (hook *ServerHook) List(projectInfo *entity.Project, resource string, pageN
[]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
alisrvAddr := projectInfo.GetApiAddr()
if alisrvAddr == "" {
return nil, nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.ProjectPo.Name)
return nil, nil, errcode.New(errcode.ServerError, "项目%v没有配置api服务器地址", projectInfo.Po.Name)
}
fields = append(fields, &dto.CommonDtoFieldDesc{
@ -23,7 +23,7 @@ func (hook *ServerHook) List(projectInfo *entity.Project, resource string, pageN
Key: "RunningStatus",
Type: "string",
HelpText: "进程运行状态:未知、运行中、停止",
Readonly: false,
Readonly: true,
Required: false,
Choices: make([]*dto.CommonDtoFieldChoice, 0),
MultiChoice: false,

View File

@ -11,11 +11,11 @@ import (
)
type ICommonResourceRepo interface {
List(projectId string, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error)
GetById(projectId string, id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error)
Create(projectId string, et dto.CommonDtoValues) (*entity.CommonResource, error)
Edit(projectId string, et dto.CommonDtoValues) error
Delete(projectId string, id int) (*entity.CommonResource, bool, error)
List(project *entity.Project, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error)
GetById(projectEt *entity.Project, id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error)
Create(projectEt *entity.Project, et dto.CommonDtoValues) (*entity.CommonResource, error)
Edit(projectEt *entity.Project, et dto.CommonDtoValues) error
Delete(projectEt *entity.Project, id int) (*entity.CommonResource, bool, error)
}
func NewCommonResourceRepo(db *gorm.DB, poTemplate model.IModel) ICommonResourceRepo {
@ -25,7 +25,7 @@ func NewCommonResourceRepo(db *gorm.DB, poTemplate model.IModel) ICommonResource
type commonResourceRepoImpl struct {
db *gorm.DB
poTemplate model.IModel
fieldsDescInfoFun func(projectId string) []*dto.CommonDtoFieldDesc
fieldsDescInfoFun func(project *entity.Project) []*dto.CommonDtoFieldDesc
}
func newCommonResourceRepoImpl(db *gorm.DB, poTemplate model.IModel) *commonResourceRepoImpl {
@ -33,7 +33,9 @@ func newCommonResourceRepoImpl(db *gorm.DB, poTemplate model.IModel) *commonReso
return &commonResourceRepoImpl{db: db, poTemplate: poTemplate, fieldsDescInfoFun: fieldsInfo}
}
func (repo *commonResourceRepoImpl) List(projectId string, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error) {
func (repo *commonResourceRepoImpl) List(projectEt *entity.Project, pageNo, pageLen int,
extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error) {
listType := reflect.New(reflect.SliceOf(reflect.TypeOf(repo.poTemplate)))
var err error
if extraQuery == "" {
@ -56,22 +58,22 @@ func (repo *commonResourceRepoImpl) List(projectId string, pageNo, pageLen int,
entityList = append(entityList, et)
}
return repo.fieldsDescInfoFun(projectId), entityList, nil
return repo.fieldsDescInfoFun(projectEt), entityList, nil
}
func (repo *commonResourceRepoImpl) GetById(projectId string, id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error) {
func (repo *commonResourceRepoImpl) GetById(projectEt *entity.Project, id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error) {
po := repo.newEmptyPo()
err := repo.db.Where("id = ?", id).Find(po).Error
err := repo.db.Where("id = ?", id).First(po).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return repo.fieldsDescInfoFun(projectId), (&entity.CommonResource{}).FromPo(repo.newEmptyPo()), false, nil
return repo.fieldsDescInfoFun(projectEt), (&entity.CommonResource{}).FromPo(repo.newEmptyPo()), false, nil
}
return nil, nil, false, errcode.New(errcode.DBError, "get resource:%v by id:%v error:%v", repo.poTemplate.TableName(), id, err)
}
return repo.fieldsDescInfoFun(projectId), (&entity.CommonResource{}).FromPo(po), true, nil
return repo.fieldsDescInfoFun(projectEt), (&entity.CommonResource{}).FromPo(po), true, nil
}
func (repo *commonResourceRepoImpl) Create(projectId string, dtoObj dto.CommonDtoValues) (*entity.CommonResource, error) {
func (repo *commonResourceRepoImpl) Create(projectEt *entity.Project, dtoObj dto.CommonDtoValues) (*entity.CommonResource, error) {
et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj)
err := repo.db.Create(et.Po).Error
if err != nil {
@ -80,7 +82,7 @@ func (repo *commonResourceRepoImpl) Create(projectId string, dtoObj dto.CommonDt
return et, nil
}
func (repo *commonResourceRepoImpl) Edit(projectId string, dtoObj dto.CommonDtoValues) error {
func (repo *commonResourceRepoImpl) Edit(projectEt *entity.Project, dtoObj dto.CommonDtoValues) error {
et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj)
err := repo.db.Where("id=?", et.Po.GetId()).Updates(et.Po).Error
if err != nil {
@ -89,8 +91,8 @@ func (repo *commonResourceRepoImpl) Edit(projectId string, dtoObj dto.CommonDtoV
return nil
}
func (repo *commonResourceRepoImpl) Delete(projectId string, id int) (*entity.CommonResource, bool, error) {
_, et, find, err := repo.GetById(projectId, id)
func (repo *commonResourceRepoImpl) Delete(projectEt *entity.Project, id int) (*entity.CommonResource, bool, error) {
_, et, find, err := repo.GetById(projectEt, id)
if err != nil {
return nil, false, err
}
@ -108,22 +110,3 @@ func (repo *commonResourceRepoImpl) Delete(projectId string, id int) (*entity.Co
func (repo *commonResourceRepoImpl) newEmptyPo() model.IModel {
return reflect.New(reflect.TypeOf(repo.poTemplate).Elem()).Interface().(model.IModel)
}
type nopRepoImpl struct {
}
func (impl *nopRepoImpl) List(projectId string, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error) {
return nil, nil, nil
}
func (impl *nopRepoImpl) GetById(projectId string, id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error) {
return nil, nil, false, nil
}
func (impl *nopRepoImpl) Create(projectId string, et dto.CommonDtoValues) (*entity.CommonResource, error) {
return nil, nil
}
func (impl *nopRepoImpl) Edit(projectId string, et dto.CommonDtoValues) error {
return nil
}
func (impl *nopRepoImpl) Delete(projectId string, id int) error {
return nil
}

View File

@ -3,24 +3,21 @@ package repo
import (
"admin/apps/game/domain/entity"
"admin/apps/game/model"
"admin/internal/consts"
"admin/apps/game/model/dto"
"admin/internal/errcode"
"errors"
"gorm.io/gorm"
)
type IProjectRepo interface {
EnsureProjectsDBData() ([]*model.Project, error)
List() ([]*entity.Project, error)
GetByProjectId(id string) (*entity.Project, bool, error)
List() ([]*dto.CommonDtoFieldDesc, []*entity.Project, error)
GetById(id int) ([]*dto.CommonDtoFieldDesc, *entity.Project, bool, error)
Create(obj dto.CommonDtoValues) (*entity.Project, error)
Edit(obj dto.CommonDtoValues) error
Delete(id int) (*entity.Project, bool, error)
}
func NewProjectRepo(db *gorm.DB) IProjectRepo {
model.GetProjectServersHandler = func(projectId string) ([]*model.Server, error) {
servers := make([]*model.Server, 0)
err := db.Where("project_id=?", projectId).Find(&servers).Error
return servers, err
}
return &projectRepoImpl{db: db}
}
@ -28,54 +25,68 @@ type projectRepoImpl struct {
db *gorm.DB
}
func (impl *projectRepoImpl) EnsureProjectsDBData() ([]*model.Project, error) {
var curProjects []*model.Project
err := impl.db.Find(&curProjects).Error
if err != nil {
return nil, errcode.New(errcode.DBError, "find all projects error:%v", err)
}
for k, v := range consts.RegisteredProjects {
find := false
for _, cur := range curProjects {
if k == cur.ProjectId {
find = true
break
}
}
if !find {
po := &model.Project{ProjectId: k, Name: v.Name}
err := impl.db.Create(po).Error
if err != nil {
return nil, errcode.New(errcode.DBError, "create project:%v error:%v", k, err)
}
curProjects = append(curProjects, po)
}
}
return curProjects, nil
var projectFieldsDescInfo []*dto.CommonDtoFieldDesc
func init() {
et := entity.FromProjectPo(&model.Project{})
projectFieldsDescInfo = et.ToCommonResource().GetDtoFieldsDescInfo(et)
}
func (impl *projectRepoImpl) List() ([]*entity.Project, error) {
func (impl *projectRepoImpl) List() ([]*dto.CommonDtoFieldDesc, []*entity.Project, error) {
list := make([]*model.Project, 0)
err := impl.db.Find(&list).Error
if err != nil {
return nil, errcode.New(errcode.DBError, "list project error:%v", err)
return projectFieldsDescInfo, nil, errcode.New(errcode.DBError, "list project error:%v", err)
}
list1 := make([]*entity.Project, 0, len(list))
for _, v := range list {
list1 = append(list1, (&entity.CommonResource{}).FromPo(v).ToProjectEntity())
list1 = append(list1, entity.FromProjectPo(v))
}
return list1, nil
return projectFieldsDescInfo, list1, nil
}
func (impl *projectRepoImpl) GetByProjectId(id string) (*entity.Project, bool, error) {
func (impl *projectRepoImpl) GetById(id int) ([]*dto.CommonDtoFieldDesc, *entity.Project, bool, error) {
po := &model.Project{}
err := impl.db.Where("project_id=?", id).Find(po).Error
err := impl.db.Where("id=?", id).First(po).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return (&entity.CommonResource{}).FromPo(po).ToProjectEntity(), false, nil
return projectFieldsDescInfo, entity.FromProjectPo(&model.Project{}), false, nil
}
}
return (&entity.CommonResource{}).FromPo(po).ToProjectEntity(), true, nil
return projectFieldsDescInfo, entity.FromProjectPo(po), true, nil
}
func (impl *projectRepoImpl) Create(obj dto.CommonDtoValues) (*entity.Project, error) {
et := entity.FromProjectPo(&model.Project{}).FromDto(obj)
err := impl.db.Create(et).Error
if err != nil {
return et, errcode.New(errcode.DBError, "create project obj:%+v error:%v", et, err)
}
return et, nil
}
func (impl *projectRepoImpl) Edit(obj dto.CommonDtoValues) error {
et := entity.FromProjectPo(&model.Project{}).FromDto(obj)
err := impl.db.Where("id=?", et.Po.GetId()).Updates(et.Po).Error
if err != nil {
return errcode.New(errcode.DBError, "edit project obj:%+v error:%v", et, err)
}
return nil
}
func (impl *projectRepoImpl) Delete(id int) (*entity.Project, bool, error) {
_, et, find, err := impl.GetById(id)
if err != nil {
return nil, false, err
}
if !find {
return et, false, nil
}
err = impl.db.Where("id=?", id).Unscoped().Delete(&model.Project{}).Error
if err != nil {
return nil, false, errcode.New(errcode.DBError, "delete project obj:%+v error:%v", id, err)
}
return et, true, nil
}

View File

@ -10,7 +10,7 @@ import (
var ServerRepoInstance IServerRepo
type IServerRepo interface {
List(projectId string) ([]*entity.CommonResource, error)
List(projectId int) ([]*entity.CommonResource, error)
}
func NewServerRepo(db *gorm.DB) IServerRepo {
@ -21,7 +21,7 @@ type serverRepoImpl struct {
db *gorm.DB
}
func (impl *serverRepoImpl) List(projectId string) ([]*entity.CommonResource, error) {
func (impl *serverRepoImpl) List(projectId int) ([]*entity.CommonResource, error) {
list := make([]*model.Server, 0)
err := impl.db.Where("project_id = ?", projectId).Find(&list).Error
if err != nil {

View File

@ -11,6 +11,7 @@ func init() {
// Account 空表就是用来兼容资源增删改查公共操作的实际列举账号等都是走各个项目api拉取
type Account struct {
ProjectId int
Account string `name:"账号" json:"account"`
ServerConfId string `name:"区服id" json:"serveId"`
RoleIds []string `gorm:"type:json;serializer:json" name:"角色id列表" json:"roleIds"`

View File

@ -2,6 +2,7 @@ package model
import (
"admin/apps/game/model/dto"
"admin/internal/consts"
"admin/internal/db"
"time"
)
@ -11,13 +12,14 @@ func init() {
}
type Ban struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string `gorm:"type:varchar(255);uniqueIndex:idx_ban"`
ServerConfID string `gorm:"type:varchar(200);uniqueIndex:idx_server" name:"区服id" required:"true" choices:"GetServerConfIDChoices" uneditable:"true"`
BanType string `name:"封禁类型" required:"true" choices:"GetBanTypeChoices" uneditable:"true"`
Value string `gorm:"type:varchar(128);uniqueIndex:idx_ban" name:"封禁值" required:"true" desc:"根据封禁类型填对应值例如ip就填ip地址" uneditable:"true"`
BanReason string `name:"封禁理由" desc:"封禁理由,会推送给客户端弹窗" required:"true"`
ExpireAt time.Time `name:"封禁到期时间" desc:"封禁到期时间0表示永久封禁"`
ID int `gorm:"primarykey" readonly:"true"`
ProjectId int `gorm:"uniqueIndex:idx_ban"`
ServerConfID string `gorm:"type:varchar(200);uniqueIndex:idx_server" name:"区服id" required:"true" choices:"GetServerConfIDChoices" uneditable:"true"`
BanType string `name:"封禁类型" required:"true" choices:"GetBanTypeChoices" uneditable:"true"`
Value string `gorm:"type:varchar(128);uniqueIndex:idx_ban" name:"封禁值" required:"true" desc:"根据封禁类型填对应值例如ip就填ip地址" uneditable:"true"`
BanReason string `name:"封禁描述" desc:"封禁描述,入库记录的描述信息" required:"true"`
BanNotifyReason string `name:"封禁弹窗" desc:"封禁弹窗内容,会推送给客户端弹窗" required:"true"`
ExpireAt time.Time `name:"封禁到期时间" desc:"封禁到期时间0表示永久封禁"`
CreatedAt time.Time `readonly:"true"`
}
@ -30,35 +32,48 @@ func (m *Ban) GetId() int {
return m.ID
}
func (m *Ban) GetServerConfIDChoices(projectId string) []*dto.CommonDtoFieldChoice {
return getChoiceServers(projectId)
func (m *Ban) GetServerConfIDChoices(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}
func (m *Ban) GetBanTypeChoices(projectId string) []*dto.CommonDtoFieldChoice {
func (m *Ban) GetBanTypeChoices(project *Project) []*dto.CommonDtoFieldChoice {
switch project.ProjectType {
case consts.RegisteredProjectId_shenmodalu:
return []*dto.CommonDtoFieldChoice{
{
Desc: "角色",
Value: consts.BanType_Role,
},
{
Desc: "角色发言",
Value: consts.BanType_RoleChat,
},
}
}
return []*dto.CommonDtoFieldChoice{
{
Desc: "账号",
Value: "account",
Value: consts.BanType_Account,
},
{
Desc: "角色",
Value: "role",
Value: consts.BanType_Role,
},
{
Desc: "IP",
Value: "ip",
Value: consts.BanType_Ip,
},
{
Desc: "账号发言",
Value: "account_chat",
Value: consts.BanType_AccountChat,
},
{
Desc: "角色发言",
Value: "role_chat",
Value: consts.BanType_RoleChat,
},
{
Desc: "设备号",
Value: "device_id",
Value: consts.BanType_Device,
},
}
}

View File

@ -11,7 +11,7 @@ func init() {
type DevicePush struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string
ProjectId int
Account string `name:"账号" desc:"给账号所属的设备做推送"`
Title string `name:"通知标题" desc:""`
Content string `name:"通知内容"`

View File

@ -6,18 +6,6 @@ type WebRspData struct {
Data any `json:"data"`
}
type ResourceInitInfo struct {
Resource string `json:"resource"`
Desc string `json:"desc"`
ShowMethods []string `json:"show_methods"`
}
type ProjectInitInfo struct {
ProjectId string `json:"project_id"`
ProjectName string `json:"project_name"`
ResourceList []*ResourceInitInfo `json:"resource_list"`
}
type CommonDtoFieldChoice struct {
Desc string `json:"desc"`
Value any `json:"value"`

View File

@ -46,11 +46,3 @@ type CommandListReq struct {
type CommandListRsp struct {
List []*PathInfo `json:"list"`
}
type ResourceListRsp struct {
List []*ResourceInitInfo `json:"list"`
}
type RoutesListRsp struct {
Projects []*ProjectInitInfo `json:"projects"`
}

View File

@ -14,7 +14,7 @@ var ProjectsAllItems = make(map[string][]*dto.CommonDtoFieldChoice)
type GlobalMail struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string
ProjectId int
ServerIDs []string `gorm:"type:json;serializer:json" name:"区服" type:"[]string" choices:"GetChoiceServers" multi_choice:"true"`
Title string `name:"邮件标题" required:"true"`
Content string `name:"邮件内容" required:"true"`
@ -31,10 +31,10 @@ func (m *GlobalMail) GetId() int {
return m.ID
}
func (m *GlobalMail) GetChoiceServers(projectId string) []*dto.CommonDtoFieldChoice {
return getChoiceServers(projectId)
func (m *GlobalMail) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}
func (m *GlobalMail) GetChoiceItems(projectId string) []*dto.CommonDtoFieldChoice {
return ProjectsAllItems[projectId]
func (m *GlobalMail) GetChoiceItems(project *Project) []*dto.CommonDtoFieldChoice {
return ProjectsAllItems[project.ProjectType]
}

View File

@ -9,10 +9,8 @@ type IModel interface {
GetId() int
}
var GetProjectServersHandler func(projectId string) ([]*Server, error)
func getChoiceServers(projectId string) []*dto.CommonDtoFieldChoice {
servers, err := GetProjectServersHandler(projectId)
func getChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
servers, err := new(Server).ListByProjectId(project.ID)
if err != nil {
panic(err)
}

View File

@ -12,7 +12,7 @@ func init() {
type Notice struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string
ProjectId int
ServerIDs []int `gorm:"type:json;serializer:json" name:"公告生效服务器" desc:"为空表示所有服" choices:"GetChoiceServers"`
Content string `name:"公告内容" required:"true"`
StartAt time.Time `name:"开始时间" required:"true"`
@ -29,6 +29,6 @@ func (m *Notice) GetId() int {
return m.ID
}
func (m *Notice) GetChoiceServers(args ...any) []*dto.CommonDtoFieldChoice {
return getChoiceServers(args[0].(string))
func (m *Notice) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -1,7 +1,12 @@
package model
import (
"admin/apps/game/model/dto"
"admin/internal/consts"
"admin/internal/db"
"admin/internal/global"
"errors"
"gorm.io/gorm"
"time"
)
@ -11,10 +16,10 @@ func init() {
// Project 游戏项目,例如神谕、神魔大陆
type Project struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string `gorm:"type:varchar(255);unique" name:"项目id" readonly:"true"`
Name string `gorm:"primarykey" required:"true" name:"项目名" readonly:"true" uneditable:"true"`
Desc string `name:"项目描述"`
ID int `gorm:"primarykey" readonly:"true"`
ProjectType string `gorm:"type:varchar(255);uniqueIndex:project" name:"项目类型" choices:"GetProjectChoices" uneditable:"true" required:"true"`
Name string `gorm:"primarykey;uniqueIndex:project" required:"true" name:"项目名" desc:"例如神魔大陆-正式服、神魔大陆-主播服" uneditable:"true"`
Desc string `name:"项目描述"`
// command_list接口服务器地址为空代表由由项目下各个逻辑服提供command_list.
// 取决于每个项目改造难度:
// 不为空就代表项目要实现一个自己统一对外暴露的gm调用服务对内聚合、分发指令执行本后台执行指令只调用一次
@ -32,3 +37,20 @@ func (lm *Project) TableName() string {
func (m *Project) GetId() int {
return m.ID
}
func (m *Project) GetProjectChoices(_ *Project) []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "神魔大陆", Value: consts.RegisteredProjectId_shenmodalu, Type: 0},
}
}
func (p *Project) GetByID() (bool, error) {
err := global.GLOB_DB.Where("id=?", p.ID).First(p).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return false, nil
}
return false, err
}
return true, nil
}

View File

@ -11,7 +11,7 @@ func init() {
type RewardCode struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string
ProjectId int
Group int `name:"奖励码组"`
Code int `name:"奖励码"`

View File

@ -11,6 +11,7 @@ func init() {
// Role 空表就是用来兼容资源增删改查公共操作的实际列举账号等都是走各个项目api拉取
type Role struct {
ProjectId int
RoleId string `name:"角色ID" json:"roleId"`
Account string `name:"账号" json:"account"`
ServerConfId string `name:"区服id" json:"serverId"`

View File

@ -18,7 +18,7 @@ type MailAttachItem struct {
type RoleMail struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string
ProjectId int
RoleIDs []string `gorm:"type:json;serializer:json" name:"生效的角色id" desc:"生效的角色id逗号分隔多个" required:"true"`
ServerID string `name:"所属区服" choices:"GetChoiceServers" required:"true"`
Title string `name:"邮件标题" required:"true"`
@ -36,10 +36,10 @@ func (m *RoleMail) GetId() int {
return m.ID
}
func (m *RoleMail) GetChoiceServers(projectId string) []*dto.CommonDtoFieldChoice {
return getChoiceServers(projectId)
func (m *RoleMail) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}
func (m *RoleMail) GetChoiceItems(projectId string) []*dto.CommonDtoFieldChoice {
return ProjectsAllItems[projectId]
func (m *RoleMail) GetChoiceItems(project *Project) []*dto.CommonDtoFieldChoice {
return ProjectsAllItems[project.ProjectType]
}

View File

@ -2,6 +2,7 @@ package model
import (
"admin/internal/db"
"admin/internal/global"
"time"
)
@ -12,7 +13,7 @@ func init() {
// Server 逻辑服
type Server struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string `gorm:"type:varchar(200);uniqueIndex:idx_server"`
ProjectId int `gorm:"uniqueIndex:idx_server"`
ServerConfID string `gorm:"type:varchar(200);uniqueIndex:idx_server" name:"区服id" required:"true" uneditable:"true"`
Desc string `name:"描述"`
// command_list接口服务器地址为空代表由由项目统一提供command_list.
@ -32,3 +33,9 @@ func (lm *Server) TableName() string {
func (m *Server) GetId() int {
return m.ID
}
func (m *Server) ListByProjectId(projectId int) ([]*Server, error) {
list := make([]*Server, 0)
err := global.GLOB_DB.Where("project_id=?", projectId).Find(&list).Error
return list, err
}

View File

@ -12,7 +12,7 @@ func init() {
type WhiteList struct {
ID int `gorm:"primarykey" readonly:"true"`
ProjectId string `gorm:"type:varchar(256);uniqueIndex:idx_whitelist"`
ProjectId int `gorm:"uniqueIndex:idx_whitelist"`
ServerConfID string `gorm:"type:varchar(200);uniqueIndex:idx_server" name:"区服id" required:"true" choices:"GetChoiceServers"`
Account string `gorm:"type:varchar(128);uniqueIndex:idx_whitelist" name:"账户" required:"true"`
Desc string `name:"描述"`
@ -28,6 +28,6 @@ func (m *WhiteList) GetId() int {
return m.ID
}
func (m *WhiteList) GetChoiceServers(projectId string) []*dto.CommonDtoFieldChoice {
return getChoiceServers(projectId)
func (m *WhiteList) GetChoiceServers(project *Project) []*dto.CommonDtoFieldChoice {
return getChoiceServers(project)
}

View File

@ -6,32 +6,10 @@ import (
"admin/internal/context"
"admin/lib/httpclient"
"admin/lib/xlog"
"strconv"
"strings"
)
func (ctl *controller) GetRoutes(ctx *context.WebContext, params *dto.NilReq, rsp *dto.RoutesListRsp) error {
projects, err := ctl.svc.GetProjectList()
if err != nil {
return err
}
for _, project := range projects {
projectDto := &dto.ProjectInitInfo{
ProjectId: project.ProjectPo.ProjectId,
ProjectName: project.ProjectPo.Name,
}
for _, v := range ctl.svc.GetSupportResourcesList() {
if v.Resource == consts.ResourcesName_Project {
continue
}
projectDto.ResourceList = append(projectDto.ResourceList, v)
}
rsp.Projects = append(rsp.Projects, projectDto)
}
return nil
}
func (ctl *controller) CommandList(ctx *context.WebContext, params *dto.CommandListReq, rsp *dto.CommandListRsp) error {
url := params.Addr + "/api/commandlist"
@ -93,7 +71,7 @@ func (ctl *controller) getProjectResourceCommandApiAddr(ctx *context.WebContext)
return ctl.svc.GetProjectInvokeApiAddr(projectId, nil)
}
func getCtxURIProjectIdAndResource(ctx *context.WebContext) (string, string) {
func getCtxURIProjectIdAndResource(ctx *context.WebContext) (int, string) {
return getCtxURIProjectId(ctx), getCtxURIResource(ctx)
}
@ -107,9 +85,10 @@ func getCtxURIResource(ctx *context.WebContext) string {
return resource
}
func getCtxURIProjectId(ctx *context.WebContext) string {
func getCtxURIProjectId(ctx *context.WebContext) int {
projectId := ctx.GinCtx().Param("projectId")
return projectId
projectId1, _ := strconv.Atoi(projectId)
return projectId1
}
func (ctl *controller) apiRequest() {

View File

@ -0,0 +1,72 @@
package server
import (
apiUser "admin/apps/user/api"
"admin/internal/context"
"admin/internal/errcode"
"admin/internal/permission"
)
func (srv *Server) CheckToken(ctx *context.WebContext) {
err := ctx.ExtractHeader()
if err != nil {
ctx.Fail(errcode.New(errcode.HeaderParamsInvalid, "header invalid"))
return
}
authRsp, err := apiUser.GetUserApiInstance().Auth(ctx, &apiUser.AuthReq{
Token: ctx.Header.Token,
UserId: ctx.Header.UserId,
})
if err != nil {
ctx.Fail(err)
return
}
ctx.GinCtx().Set("userInfo", authRsp)
return
}
func (srv *Server) CheckPermissions(ctx *context.WebContext) {
userInfoI, find := ctx.GinCtx().Get("userInfo")
if !find {
ctx.Fail(errcode.New(errcode.TokenInvalid, "not found ctx userInfo"))
return
}
userInfo, _ := userInfoI.(*apiUser.UserInfo)
projectId, resource := getCtxURIProjectIdAndResource(ctx)
if projectId <= 0 {
if ctx.GinCtx().Request.URL.Path == "/api/project" {
if userInfo.Character != "admin" {
ctx.Fail(errcode.New(errcode.NoPermission, "user %v is not admin, can't operate project", ctx.Header.UserId))
return
}
ctx.GinCtx().Next()
return
}
ctx.Fail(errcode.New(errcode.NoPermission, "project_id %v in ctx invalid", projectId))
return
}
if resource == "" {
ctx.Fail(errcode.New(errcode.NoPermission, "resource %v in ctx invalid", resource))
return
}
reqPermission := permission.GetProjectResourcePermission(projectId, resource, ctx.GinCtx().Request.Method)
if userInfo.Character == "admin" {
ctx.GinCtx().Next()
return
}
for _, v := range userInfo.Permissions {
if reqPermission == v {
ctx.GinCtx().Next()
return
}
}
ctx.Fail(errcode.New(errcode.NoPermission, "user %v don't have permission %v", ctx.Header.UserId, reqPermission))
return
}

View File

@ -25,6 +25,4 @@ func (srv *Server) Route(engine *web.Engine) {
resourceUnderProjectGroup.Delete("", "删除", consts.WebPathPermit_Read, srv.ctl.CommonDelete)
}
}
apiGroup.Get("/routes", "获取路由列表,用于客户端生成前端操作菜单", consts.WebPathPermit_Read, srv.ctl.GetRoutes)
}

View File

@ -1,6 +1,7 @@
package service
import (
"admin/apps/game/api"
"admin/apps/game/domain"
"admin/apps/game/model/dto"
"admin/internal/consts"
@ -20,14 +21,15 @@ func New(db *gorm.DB) (*Service, error) {
resourceSvc: domain.NewCommonResourceService(db),
projectSvc: domain.NewProjectService(db),
}
err := svc.ensureProjectsDBData()
if err != nil {
return nil, err
}
api.RegisterGameApi(svc)
//err := svc.ensureProjectsDBData()
//if err != nil {
// return nil, err
//}
return svc, nil
}
func (svc *Service) CommonList(ctx context.Context, projectId string, resourceName string, params *dto.CommonListReq) (*dto.CommonDtoList, error) {
func (svc *Service) CommonList(ctx context.Context, projectId int, resourceName string, params *dto.CommonListReq) (*dto.CommonDtoList, error) {
var (
query string
args []any
@ -42,24 +44,24 @@ func (svc *Service) CommonList(ctx context.Context, projectId string, resourceNa
return &dto.CommonDtoList{FieldsDesc: fieldsDescInfo, Rows: rows}, err
}
func (svc *Service) CommonPost(ctx context.Context, projectId string, resourceName string, params dto.CommonDtoValues) (dto.CommonDtoValues, error) {
func (svc *Service) CommonPost(ctx context.Context, projectId int, resourceName string, params dto.CommonDtoValues) (dto.CommonDtoValues, error) {
if resourceName != consts.ResourcesName_Project {
params["ProjectId"] = projectId
}
return svc.resourceSvc.Create(projectId, resourceName, params)
}
func (svc *Service) CommonPut(ctx context.Context, projectId string, resourceName string, params dto.CommonDtoValues) error {
func (svc *Service) CommonPut(ctx context.Context, projectId int, resourceName string, params dto.CommonDtoValues) error {
if resourceName != consts.ResourcesName_Project {
params["ProjectId"] = projectId
}
return svc.resourceSvc.Edit(projectId, resourceName, params)
}
func (svc *Service) CommonDelete(ctx context.Context, projectId string, resourceName string, id int) error {
func (svc *Service) CommonDelete(ctx context.Context, projectId int, resourceName string, id int) error {
return svc.resourceSvc.Delete(projectId, resourceName, id)
}
func (svc *Service) GetSupportResourcesList() []*dto.ResourceInitInfo {
return svc.resourceSvc.GetSupportResourcesList()
func (svc *Service) GetSupportResourcesList(permissions []string) []*api.ResourceInitInfo {
return svc.resourceSvc.GetSupportResourcesList(permissions)
}

View File

@ -1,15 +1,68 @@
package service
import "admin/apps/game/domain/entity"
import (
"admin/apps/game/api"
"admin/apps/game/domain/entity"
"admin/internal/consts"
"admin/internal/permission"
)
func (svc *Service) GetRoutes(req *api.GetRoutesReq) (*api.GetRoutesRsp, error) {
rsp := &api.GetRoutesRsp{}
rsp.Projects = make([]*api.ProjectInitInfo, 0)
projects, err := svc.GetProjectList()
if err != nil {
return nil, err
}
for _, project := range projects {
projectDto := &api.ProjectInitInfo{
ProjectId: project.Po.ID,
ProjectName: project.Po.Name,
}
for _, v := range svc.GetSupportResourcesList(req.Permissions) {
if v.Resource == consts.ResourcesName_Project {
continue
}
permitV := &api.ResourceInitInfo{
Resource: v.Resource,
Desc: v.Desc,
ShowMethods: make([]string, 0),
MethodsPermissionStr: make([]string, 0),
}
for _, resourceMethod := range v.ShowMethods {
curMethodPermissionStr := permission.GetProjectResourcePermission(project.Po.ID, v.Resource, resourceMethod)
v.MethodsPermissionStr = append(v.MethodsPermissionStr, curMethodPermissionStr)
findPermit := false
for _, hasPermission := range req.Permissions {
if curMethodPermissionStr == hasPermission {
// 有权限
findPermit = true
break
}
}
if req.IsAdmin || findPermit {
permitV.ShowMethods = append(permitV.ShowMethods, resourceMethod)
permitV.MethodsPermissionStr = append(permitV.MethodsPermissionStr, curMethodPermissionStr)
}
}
projectDto.ResourceList = append(projectDto.ResourceList, permitV)
projectDto.ResourceTotalList = append(projectDto.ResourceList, v)
}
rsp.Projects = append(rsp.Projects, projectDto)
}
return rsp, nil
}
func (svc *Service) GetProjectList() ([]*entity.Project, error) {
return svc.projectSvc.List()
_, _, list, err := svc.projectSvc.List()
return list, err
}
func (svc *Service) ensureProjectsDBData() error {
return svc.projectSvc.EnsureProjectsDBData()
}
func (svc *Service) GetProjectInvokeApiAddr(projectId string, serverIds []int) ([]string, error) {
func (svc *Service) GetProjectInvokeApiAddr(projectId int, serverIds []int) ([]string, error) {
return svc.projectSvc.GetProjectInvokeApiAddr(projectId, serverIds)
}

View File

@ -0,0 +1,40 @@
package api
import "context"
var userApiInstance IUserApi
func GetUserApiInstance() IUserApi {
return userApiInstance
}
type IUserApi interface {
Auth(ctx context.Context, req *AuthReq) (*AuthRsp, error)
}
func RegisterUserApiHandler(handler IUserApi) {
userApiInstance = handler
}
type AuthReq struct {
Token string
UserId int
Url string
}
type UserInfo struct {
UserId int `json:"user_id"`
NickName string `json:"nick_name"`
Icon string `json:"icon"`
Character string `json:"character"`
Permissions []string `json:"permissions"`
}
type TokenInfo struct {
Token string `json:"token"`
ExpireAt int64 `json:"expire_at"`
}
type AuthRsp struct {
User *UserInfo
Token *TokenInfo
}

View File

@ -1,8 +1,19 @@
package user
import "admin/lib/node"
import (
"admin/apps/user/server"
"admin/apps/user/service"
"admin/internal/global"
"admin/lib/node"
)
func initFun(app *node.Application) error {
svc, err := service.New(global.GLOB_DB) // 初始化应用服务
if err != nil {
panic(err)
}
srv := server.New(svc) // 初始化http服务
srv.Route(global.GLOB_API_ENGINE) // 初始化http服务路由
return nil
}

View File

@ -0,0 +1,146 @@
package domain
import (
"admin/apps/user/domain/entity"
"admin/apps/user/domain/repo"
"admin/apps/user/model"
"admin/apps/user/model/dto"
"admin/internal/consts"
"fmt"
"gorm.io/gorm"
)
type CommonResourceService struct {
userRepo repo.IUserRepo
tokenRepo repo.ITokenRepo
}
func initCommonResourcesRepo(db *gorm.DB) {
r(consts.ResourcesName_SysUser, "用户管理", repo.NewCommonResourceRepo(db, &model.User{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
r(consts.ResourcesName_SysCharacter, "角色管理", repo.NewCommonResourceRepo(db, &model.Character{}), ShowMethod_Get|ShowMethod_Post|ShowMethod_Put|ShowMethod_Delete)
}
func NewCommonResourceService(db *gorm.DB) *CommonResourceService {
initCommonResourcesRepo(db)
svc := &CommonResourceService{
userRepo: repo.NewUserRepo(db),
tokenRepo: repo.NewTokenRepo(db),
}
return svc
}
func (svc *CommonResourceService) List(resource string, pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []dto.CommonDtoValues, error) {
fieldsDescInfo, etList, err := findCommResourceRepo(resource).List(pageNo, pageLen, extraQuery, args...)
if err != nil {
return nil, nil, err
}
retList := make([]dto.CommonDtoValues, 0, len(etList))
for _, v := range etList {
retList = append(retList, v.ToCommonDto())
}
return fieldsDescInfo, retList, nil
}
func (svc *CommonResourceService) GetById(resource string, id int) ([]*dto.CommonDtoFieldDesc, dto.CommonDtoValues, *entity.CommonResource, bool, error) {
fieldsDescInfo, et, find, err := findCommResourceRepo(resource).GetById(id)
if err != nil {
return nil, nil, nil, false, err
}
return fieldsDescInfo, et.ToCommonDto(), et, find, nil
}
func (svc *CommonResourceService) Create(resource string, dtoObj dto.CommonDtoValues) (dto.CommonDtoValues, *entity.CommonResource, error) {
if resource == consts.ResourcesName_SysUser {
}
et, err := findCommResourceRepo(resource).Create(dtoObj)
if err != nil {
return nil, et, err
}
// 返回新的实体插入数据库之后会填入数据库id所以要返给前端刷新id数据
return et.ToCommonDto(), et, err
}
func (svc *CommonResourceService) Edit(resource string, dtoObj dto.CommonDtoValues) error {
err := findCommResourceRepo(resource).Edit(dtoObj)
if err != nil {
return err
}
return nil
}
func (svc *CommonResourceService) Delete(resource string, id int) error {
_, _, err := findCommResourceRepo(resource).Delete(id)
if err != nil {
return err
}
return nil
}
func (svc *CommonResourceService) GetSupportResourcesList() []*dto.ResourceInitInfo {
list := make([]*dto.ResourceInitInfo, 0, len(commResourcesRepo))
for _, v := range commResourcesRepo {
list = append(list, &dto.ResourceInitInfo{
Resource: v.Resource,
Desc: v.Desc,
ShowMethods: v.ShowMethods,
})
}
return list
}
const (
ShowMethod_Get = 1
ShowMethod_Post = 1 << 1
ShowMethod_Put = 1 << 2
ShowMethod_Delete = 1 << 3
)
type resourceRepoInfo struct {
Resource string
Desc string
Repo repo.ICommonResourceRepo
ShowMethods []string
showMethods int
}
var commResourcesRepo = make([]*resourceRepoInfo, 0)
func r(resource, desc string, repo repo.ICommonResourceRepo, showMethods int) {
curRepo := &resourceRepoInfo{
Resource: resource,
Desc: desc,
Repo: repo,
}
if showMethods&ShowMethod_Get == ShowMethod_Get {
curRepo.ShowMethods = append(curRepo.ShowMethods, "get")
}
if showMethods&ShowMethod_Post == ShowMethod_Post {
curRepo.ShowMethods = append(curRepo.ShowMethods, "post")
}
if showMethods&ShowMethod_Put == ShowMethod_Put {
curRepo.ShowMethods = append(curRepo.ShowMethods, "put")
}
if showMethods&ShowMethod_Delete == ShowMethod_Delete {
curRepo.ShowMethods = append(curRepo.ShowMethods, "delete")
}
commResourcesRepo = append(commResourcesRepo, curRepo)
}
func findCommResourceRepo(resource string) repo.ICommonResourceRepo {
for _, v := range commResourcesRepo {
if v.Resource == resource {
return v.Repo
}
}
panic(fmt.Errorf("not found Resource:%v", resource))
return nil
}

View File

@ -0,0 +1,67 @@
package entity
import (
"admin/apps/user/model"
"admin/apps/user/model/dto"
"reflect"
)
type CommonResource struct {
Po model.IModel
}
func (et *CommonResource) FromPo(po model.IModel) *CommonResource {
et.Po = po
return et
}
func (et *CommonResource) FromDto(dto dto.CommonDtoValues) *CommonResource {
po := et.Po
//to := reflect.TypeOf(Po)
vo := reflect.ValueOf(po).Elem()
to := reflect.TypeOf(po).Elem()
for k, v := range dto {
fo := vo.FieldByName(k)
ft, find := to.FieldByName(k)
if !find {
continue
}
fo.Set(parseStr2FieldValue(ft, v))
}
return et
}
func (et *CommonResource) ToPo() model.IModel {
return et.Po
}
func (et *CommonResource) ToCommonDto() dto.CommonDtoValues {
return poToCommonDtoNo(et.Po)
}
func (et *CommonResource) GetDtoFieldsDescInfo() []*dto.CommonDtoFieldDesc {
ptrVo := reflect.ValueOf(et.Po)
to := reflect.TypeOf(et.Po).Elem()
vo := ptrVo.Elem()
obj := make([]*dto.CommonDtoFieldDesc, 0, to.NumField())
for i := 0; i < vo.NumField(); i++ {
ft := to.Field(i)
//fo := vo.Field(i)
if ft.Tag.Get("server_use") == "true" {
continue
}
f1 := getFieldTypeDtoDescInfo(ptrVo, ft)
obj = append(obj, f1)
}
return obj
}

View File

@ -0,0 +1,36 @@
package entity
import (
"admin/apps/user/model"
"admin/apps/user/model/dto"
)
type User struct {
Po *model.User
Character *model.Character
}
func FromUserPo(po *model.User, cPo *model.Character) *User {
return &User{Po: po, Character: cPo}
}
func (et *User) GetUserId() int {
return et.Po.ID
}
func (et *User) IsAdmin() bool {
return et.Character.Name == "admin"
}
// GetPermissions 返回权限列表
func (et *User) GetPermissions() []string {
return et.Character.Permissions
}
func (et *User) ToDtoInfo() *dto.UserInfo {
return &dto.UserInfo{
UserId: et.Po.ID,
NickName: et.Po.NickName,
Character: et.Po.CharacterName,
}
}

View File

@ -0,0 +1,186 @@
package entity
import (
"admin/apps/user/model/dto"
"admin/lib/xlog"
"fmt"
"gorm.io/gorm"
"reflect"
"strconv"
"strings"
"time"
)
func poToCommonDtoNo(po any) dto.CommonDtoValues {
obj := make(dto.CommonDtoValues)
to := reflect.TypeOf(po).Elem()
vo := reflect.ValueOf(po).Elem()
for i := 0; i < vo.NumField(); i++ {
ft := to.Field(i)
fo := vo.Field(i)
if ft.Tag.Get("server_use") == "true" {
continue
}
if ft.Name == "UserPass" {
obj[ft.Name] = "***" // 密码不下发了
} else {
obj[ft.Name] = fo.Interface()
}
if ft.Type.Name() == "Time" && ft.Type.PkgPath() == "time" {
obj[ft.Name] = fo.Interface().(time.Time).Format("2006/01/02 15:04:05")
}
}
return obj
}
func getFieldTypeDtoDescInfo(poValue reflect.Value, fieldType reflect.StructField) *dto.CommonDtoFieldDesc {
name := fieldType.Tag.Get("name")
if name == "" {
name = fieldType.Name
}
f1 := &dto.CommonDtoFieldDesc{
Name: name,
Key: fieldType.Name,
Type: fieldType.Type.Name(),
HelpText: fieldType.Tag.Get("desc"),
Readonly: fieldType.Tag.Get("readonly") == "true",
Required: fieldType.Tag.Get("required") == "true",
Choices: make([]*dto.CommonDtoFieldChoice, 0),
MultiChoice: fieldType.Tag.Get("multi_choice") == "true",
Uneditable: fieldType.Tag.Get("uneditable") == "true",
}
if f1.Key == "CreatedAt" {
f1.Name = "创建时间"
}
if tagType := fieldType.Tag.Get("type"); tagType != "" {
f1.Type = tagType
}
cf := fieldType.Tag.Get("choices")
if cf != "" {
method := poValue.MethodByName(cf)
if !method.IsValid() {
xlog.Warnf("po %v choices %v function not found in model", poValue.Type().Name(), cf)
} else {
rets := method.Call([]reflect.Value{})
f1.Choices = rets[0].Interface().([]*dto.CommonDtoFieldChoice)
}
}
return f1
}
func parseStr2FieldValue(field reflect.StructField, rawValue any) (realSetValue reflect.Value) {
setValue := fmt.Sprintf("%v", rawValue)
var parsedValue any
switch field.Type.Kind() {
case reflect.Int:
v, _ := strconv.Atoi(setValue)
parsedValue = int(v)
case reflect.Int32:
v, _ := strconv.Atoi(setValue)
parsedValue = int32(v)
case reflect.Int8:
v, _ := strconv.Atoi(setValue)
parsedValue = int8(v)
case reflect.Int16:
v, _ := strconv.Atoi(setValue)
parsedValue = int16(v)
case reflect.Int64:
v, _ := strconv.Atoi(setValue)
parsedValue = int64(v)
case reflect.Uint:
v, _ := strconv.Atoi(setValue)
parsedValue = uint(v)
case reflect.Uint8:
v, _ := strconv.Atoi(setValue)
parsedValue = uint(v)
case reflect.Uint16:
v, _ := strconv.Atoi(setValue)
parsedValue = uint16(v)
case reflect.Uint32:
v, _ := strconv.Atoi(setValue)
parsedValue = uint32(v)
case reflect.Uint64:
v, _ := strconv.Atoi(setValue)
parsedValue = uint64(v)
case reflect.Bool:
parsedValue = setValue == "true"
case reflect.String:
parsedValue = setValue
case reflect.Float32:
v, _ := strconv.ParseFloat(setValue, 10)
parsedValue = float32(v)
case reflect.Float64:
v, _ := strconv.ParseFloat(setValue, 10)
parsedValue = float64(v)
case reflect.Struct:
typeName := field.Type.Name()
if typeName == "Time" {
xlog.Debugf("time content:%v", setValue)
t, _ := time.ParseInLocation("2006/01/02 15:04:05", setValue, time.Local)
return reflect.ValueOf(t)
}
if typeName == "DeletedAt" {
return reflect.ValueOf(gorm.DeletedAt{})
}
fallthrough
case reflect.Slice:
typeName := field.Type.String()
if typeName == "[]string" {
list := make([]string, 0)
if rawValueStr, ok := rawValue.(string); ok {
elems := strings.Split(rawValueStr, ",")
list = append(list, elems...)
// 空的字符串
} else {
if rawValueList, ok := rawValue.([]string); ok {
for _, v := range rawValueList {
list = append(list, v)
}
} else {
for _, v := range rawValue.([]interface{}) {
list = append(list, v.(string))
}
}
}
parsedValue = list
}
//else if typeName == "[]*model.MailAttachItem" {
// items := make([]*model.MailAttachItem, 0)
// if rawValueList, ok := rawValue.([]*model.MailAttachItem); ok {
// for _, item := range rawValueList {
// items = append(items, item)
// }
// } else {
// for _, itemI := range rawValue.([]interface{}) {
// item := &model.MailAttachItem{}
// for k, vI := range itemI.(map[string]any) {
// v := vI.(float64)
// if k == "id" {
// item.ID = int32(v)
// } else if k == "num" {
// item.Num = int64(v)
// } else if k == "item_type" {
// item.ItemType = int(v)
// }
// }
// items = append(items, item)
// }
// }
//
// parsedValue = items
//}
default:
panic(fmt.Errorf("暂时不支持的前后端交互字段类型:%v, 类型名:%v", field.Type.Kind(), field.Type.Name()))
}
return reflect.ValueOf(parsedValue)
}

View File

@ -0,0 +1,19 @@
package domain
import (
"admin/apps/game/model/dto"
)
type IRestfulEntity interface {
ToCommonDto() dto.CommonDtoValues
}
type IRestfulResourceSvc interface {
List(pageNo, pageLen int, whereValues ...string) ([]*dto.CommonDtoFieldDesc, []IRestfulEntity, error)
Post(obj dto.CommonDtoValues) (IRestfulEntity, error)
Put(obj dto.CommonDtoValues) (IRestfulEntity, error)
Delete(id int) error
}
type IResourceOpPostHook interface {
}

View File

@ -0,0 +1,126 @@
package repo
import (
"admin/apps/user/domain/entity"
"admin/apps/user/model"
"admin/apps/user/model/dto"
"admin/internal/errcode"
"admin/lib/passlib"
"admin/lib/xlog"
"errors"
"gorm.io/gorm"
"reflect"
)
type ICommonResourceRepo interface {
List(pageNo, pageLen int, extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error)
GetById(id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error)
Create(et dto.CommonDtoValues) (*entity.CommonResource, error)
Edit(et dto.CommonDtoValues) error
Delete(id int) (*entity.CommonResource, bool, error)
}
func NewCommonResourceRepo(db *gorm.DB, poTemplate model.IModel) ICommonResourceRepo {
return newCommonResourceRepoImpl(db, poTemplate)
}
type commonResourceRepoImpl struct {
db *gorm.DB
poTemplate model.IModel
fieldsDescInfoFun func() []*dto.CommonDtoFieldDesc
}
func newCommonResourceRepoImpl(db *gorm.DB, poTemplate model.IModel) *commonResourceRepoImpl {
fieldsInfo := (&entity.CommonResource{}).FromPo(poTemplate).GetDtoFieldsDescInfo
return &commonResourceRepoImpl{db: db, poTemplate: poTemplate, fieldsDescInfoFun: fieldsInfo}
}
func (repo *commonResourceRepoImpl) List(pageNo, pageLen int,
extraQuery string, args ...any) ([]*dto.CommonDtoFieldDesc, []*entity.CommonResource, error) {
listType := reflect.New(reflect.SliceOf(reflect.TypeOf(repo.poTemplate)))
var err error
if extraQuery == "" {
err = repo.db.Find(listType.Interface()).Error
} else {
err = repo.db.Where(extraQuery, args...).Find(listType.Interface()).Error
}
if err != nil {
return nil, nil, errcode.New(errcode.DBError, "list resource %v error:%v", repo.poTemplate.TableName(), err)
}
listType1 := listType.Elem()
listLen := listType1.Len()
entityList := make([]*entity.CommonResource, 0, listLen)
for i := 0; i < listType1.Len(); i++ {
po := listType1.Index(i).Interface().(model.IModel)
et := &entity.CommonResource{}
et.FromPo(po)
entityList = append(entityList, et)
}
return repo.fieldsDescInfoFun(), entityList, nil
}
func (repo *commonResourceRepoImpl) GetById(id int) ([]*dto.CommonDtoFieldDesc, *entity.CommonResource, bool, error) {
po := repo.newEmptyPo()
err := repo.db.Where("id = ?", id).First(po).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return repo.fieldsDescInfoFun(), (&entity.CommonResource{}).FromPo(repo.newEmptyPo()), false, nil
}
return nil, nil, false, errcode.New(errcode.DBError, "get resource:%v by id:%v error:%v", repo.poTemplate.TableName(), id, err)
}
return repo.fieldsDescInfoFun(), (&entity.CommonResource{}).FromPo(po), true, nil
}
func (repo *commonResourceRepoImpl) Create(dtoObj dto.CommonDtoValues) (*entity.CommonResource, error) {
if pass, find := dtoObj["UserPass"]; find {
dtoObj["UserPass"] = passlib.EncryptPassword(pass.(string))
}
et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj)
err := repo.db.Create(et.Po).Error
if err != nil {
return et, errcode.New(errcode.DBError, "create resource:%v obj:%+v error:%v", repo.poTemplate.TableName(), et, err)
}
return et, nil
}
func (repo *commonResourceRepoImpl) Edit(dtoObj dto.CommonDtoValues) error {
xlog.Infof("update obj:%+v", dtoObj)
if pass, find := dtoObj["UserPass"]; find {
if pass.(string) == "***" {
// 没有改密码
delete(dtoObj, "UserPass")
} else {
dtoObj["UserPass"] = passlib.EncryptPassword(pass.(string))
}
}
et := (&entity.CommonResource{}).FromPo(repo.newEmptyPo()).FromDto(dtoObj)
err := repo.db.Where("id=?", et.Po.GetId()).Updates(et.Po).Error
if err != nil {
return errcode.New(errcode.DBError, "edit resource:%v obj:%+v error:%v", repo.poTemplate.TableName(), et, err)
}
return nil
}
func (repo *commonResourceRepoImpl) Delete(id int) (*entity.CommonResource, bool, error) {
_, et, find, err := repo.GetById(id)
if err != nil {
return nil, false, err
}
if !find {
return et, false, nil
}
err = repo.db.Where("id=?", id).Unscoped().Delete(repo.poTemplate).Error
if err != nil {
return nil, false, errcode.New(errcode.DBError, "delete resource:%v obj:%+v error:%v", repo.poTemplate.TableName(), id, err)
}
return et, true, nil
}
func (repo *commonResourceRepoImpl) newEmptyPo() model.IModel {
return reflect.New(reflect.TypeOf(repo.poTemplate).Elem()).Interface().(model.IModel)
}

View File

@ -0,0 +1,71 @@
package repo
import (
"admin/apps/user/model"
"admin/lib/tokenlib"
"errors"
"gorm.io/gorm"
"time"
)
type ITokenRepo interface {
GetToken(token string) (*model.Token, bool, error)
GetTokenByUserId(userId int) (*model.Token, bool, error)
CreateToken(userId int, dura time.Duration) (*model.Token, error)
DeleteToken(token string) error
DeleteTokenByUserId(userId int) error
}
func NewTokenRepo(db *gorm.DB) ITokenRepo {
return &tokenRepoImpl{db: db}
}
type tokenRepoImpl struct {
db *gorm.DB
}
func (impl *tokenRepoImpl) GetToken(token string) (*model.Token, bool, error) {
info := &model.Token{}
err := impl.db.Where("token = ?", token).First(info).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return info, false, nil
}
return nil, false, err
}
return info, true, nil
}
func (impl *tokenRepoImpl) GetTokenByUserId(userId int) (*model.Token, bool, error) {
info := &model.Token{}
err := impl.db.Where("user_id = ?", userId).First(info).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return info, false, nil
}
return nil, false, err
}
return info, true, nil
}
func (impl *tokenRepoImpl) CreateToken(userId int, dura time.Duration) (*model.Token, error) {
tokenStr, err := tokenlib.GenToken(userId, dura)
if err != nil {
return nil, err
}
po := &model.Token{
Token: tokenStr,
UserId: userId,
ExpireAt: time.Now().Add(dura),
}
err = impl.db.Create(po).Error
return po, err
}
func (impl *tokenRepoImpl) DeleteToken(token string) error {
return impl.db.Where("token = ?", token).Unscoped().Delete(new(model.Token)).Error
}
func (impl *tokenRepoImpl) DeleteTokenByUserId(userId int) error {
return impl.db.Where("user_id = ?", userId).Unscoped().Delete(new(model.Token)).Error
}

View File

@ -0,0 +1,95 @@
package repo
import (
"admin/apps/user/domain/entity"
"admin/apps/user/model"
"admin/internal/errcode"
"admin/lib/passlib"
"errors"
"gorm.io/gorm"
"strings"
)
type IUserRepo interface {
GetById(userId int) (*entity.User, bool, error)
GetByUser(user string) (*entity.User, bool, error)
CreateAdminUsers() error
}
func NewUserRepo(db *gorm.DB) IUserRepo {
return &userRepoImpl{db: db}
}
type userRepoImpl struct {
db *gorm.DB
}
func (impl *userRepoImpl) CreateAdminUsers() error {
adminCharacter := &model.Character{
Name: "admin",
}
adminList := []*model.User{
&model.User{
UserName: "admin",
NickName: "admin",
UserPass: passlib.EncryptPassword("admin@123"),
CharacterName: adminCharacter.Name,
},
}
if err := impl.db.Create(adminCharacter).Error; err != nil {
if !strings.Contains(err.Error(), "Duplicate entry") {
return errcode.New(errcode.DBError, "create admin character fail:%v", err)
}
}
if err := impl.db.Create(adminList).Error; err != nil {
if !strings.Contains(err.Error(), "Duplicate entry") {
return errcode.New(errcode.DBError, "create admin fail:%v", err)
}
}
return nil
}
func (impl *userRepoImpl) GetById(userId int) (*entity.User, bool, error) {
po := new(model.User)
chPo := new(model.Character)
err := impl.db.Where("id = ?", userId).First(po).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return entity.FromUserPo(po, chPo), false, nil
}
return nil, false, errcode.New(errcode.DBError, "get user by id:%v error:%v", userId, err)
}
err = impl.db.Where("name = ?", po.CharacterName).First(chPo).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return entity.FromUserPo(po, chPo), false, nil
}
return nil, false, errcode.New(errcode.DBError, "get character by id:%v error:%v", po.CharacterName, err)
}
return entity.FromUserPo(po, chPo), true, nil
}
func (impl *userRepoImpl) GetByUser(user string) (*entity.User, bool, error) {
po := new(model.User)
chPo := new(model.Character)
err := impl.db.Where("user_name = ?", user).First(po).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return entity.FromUserPo(po, chPo), false, nil
}
return nil, false, errcode.New(errcode.DBError, "get user by id:%v error:%v", user, err)
}
err = impl.db.Where("name = ?", po.CharacterName).First(chPo).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return entity.FromUserPo(po, chPo), false, nil
}
return nil, false, errcode.New(errcode.DBError, "get character by id:%v error:%v", po.CharacterName, err)
}
return entity.FromUserPo(po, chPo), true, nil
}

View File

@ -0,0 +1,64 @@
package domain
import (
"admin/apps/user/domain/entity"
"admin/apps/user/model"
"admin/internal/errcode"
"admin/lib/passlib"
"time"
)
func (svc *CommonResourceService) EnsureInitDB() error {
return svc.userRepo.CreateAdminUsers()
}
func (svc *CommonResourceService) Login(user, pwd string) (*entity.User, error) {
userEt, find, err := svc.userRepo.GetByUser(user)
if err != nil {
return nil, err
}
if !find {
return nil, errcode.New(errcode.UserOrPassInValid, "")
}
if !passlib.ComparePassword(userEt.Po.UserPass, pwd) {
return nil, errcode.New(errcode.UserOrPassInValid, "")
}
return userEt, nil
}
func (svc *CommonResourceService) GetUserById(userId int) (*entity.User, bool, error) {
et, find, err := svc.userRepo.GetById(userId)
if err != nil {
return et, false, err
}
return et, find, nil
}
func (svc *CommonResourceService) GetOrCreateToken(userId int) (*model.Token, error) {
tokenInfo, find, err := svc.tokenRepo.GetTokenByUserId(userId)
if err != nil {
return nil, err
}
if !find {
tokenInfo, err = svc.tokenRepo.CreateToken(userId, time.Hour*24*15) // 15天过期的token可以使用中加上token刷新功能
if err != nil {
return nil, err
}
}
return tokenInfo, nil
}
func (svc *CommonResourceService) GetToken(userId int) (*model.Token, error) {
tokenInfo, find, err := svc.tokenRepo.GetTokenByUserId(userId)
if err != nil {
return nil, err
}
if !find {
return tokenInfo, errcode.New(errcode.TokenInvalid, "user %v token not found", userId)
}
return tokenInfo, nil
}

View File

@ -0,0 +1,43 @@
package model
import (
"admin/internal/db"
"admin/internal/global"
"admin/lib/xlog"
"time"
)
func init() {
db.RegisterTableModels(Character{})
}
type Permission struct {
}
// Character 角色权限组
type Character struct {
ID int `gorm:"primarykey" readonly:"true"`
Name string `name:"角色名" desc:"区别一组用户的名字例如qa、策划、运营" gorm:"type:varchar(255);unique" required:"true" uneditable:"true"`
// 权限列表,格式就是 ["project:<projectId>:<resource>:<get>", "sys:user:get", ...]
// 例如项目3封禁功能的列表获取权限"project:3:ban:list"
Permissions []string `gorm:"type:json;serializer:json" name:"权限列表" type:"Permissions"`
CreatedAt time.Time `readonly:"true"`
}
func (m *Character) TableName() string {
return "character"
}
func (m *Character) GetId() int {
return m.ID
}
func (m *Character) List() []*Character {
list := make([]*Character, 0)
err := global.GLOB_DB.Find(&list).Error
if err != nil {
xlog.Errorf("get all character error:%v", err)
return list
}
return list
}

View File

@ -0,0 +1,64 @@
package dto
type WebRspData struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data"`
}
type ResourceInitInfo struct {
Resource string `json:"resource"`
Desc string `json:"desc"`
ShowMethods []string `json:"show_methods"`
}
type CommonDtoFieldChoice struct {
Desc string `json:"desc"`
Value any `json:"value"`
// 描述选项的类型,例如添加物品时,可以添加道具、翅膀、宠物等,他们可能不一定都设计为道具
Type int `json:"type"`
}
type CommonDtoFieldDesc struct {
Name string `json:"name"`
Key string `json:"key"`
// 字段类型基础类型支持int float string bool []<基础类行>
// 支持自定义类型和自定义类型的数组
Type string `json:"type"`
HelpText string `json:"help_text"`
Readonly bool `json:"readonly"` // 是否只读,就只展示在表格中
Required bool `json:"required"` // 是否必填,不能为空
Choices []*CommonDtoFieldChoice `json:"choices"` // 可选项,用于字段做下拉框
MultiChoice bool `json:"multi_choice"` // 是否多选
Uneditable bool `json:"uneditable"` // 不可编辑某些数据一旦新增之后不能修改例如封禁的值、服务器的id等
}
//type CommonDtoValue struct {
// FieldName string `json:"field_name"`
// Value any `json:"value"`
//}
type CommonDtoValues map[string]any
type CommonDtoList struct {
FieldsDesc []*CommonDtoFieldDesc `json:"fields_desc"` // 数据字段描述信息
Rows []CommonDtoValues `json:"rows"` // 数据行
}
type PathInfo struct {
Path string `json:"path"`
Method string `json:"method"`
}
type UserInfo struct {
UserId int `json:"user_id"`
NickName string `json:"nick_name"`
Icon string `json:"icon"`
Character string `json:"character"`
Permissions []string `json:"permissions"`
}
type TokenInfo struct {
Token string `json:"token"`
ExpireAt int64 `json:"expire_at"`
}

View File

@ -0,0 +1,67 @@
package dto
import "admin/apps/game/api"
type NilReq struct {
}
type NilRsp = NilReq
type CommonListReq struct {
PageNo int `json:"page_no"`
PageLen int `json:"page_len"`
WhereValue1 string `json:"where_value1"`
WhereValue2 string `json:"where_value2"`
WhereValue3 string `json:"where_value3"`
}
type CommonPostReq struct {
Dto *CommonDtoValues `json:"dto"`
}
type CommonPutReq struct {
Dto *CommonDtoValues `json:"dto"`
}
type CommonDeleteReq struct {
Id int `json:"id"`
}
type CommonListRsp = CommonDtoList
type CommonPostRsp struct {
Dto *CommonDtoValues `json:"dto"`
}
type CommonPutRsp struct {
Dto *CommonDtoValues `json:"dto"`
}
type CommonDeleteRsp struct {
Id int `json:"id"`
}
type CommandListReq struct {
Addr string `json:"addr"`
}
type CommandListRsp struct {
List []*PathInfo `json:"list"`
}
type ResourceListRsp struct {
List []*api.ResourceInitInfo `json:"list"`
}
type LoginReq struct {
User string `json:"user"`
Password string `json:"password"`
}
type LoginRsp struct {
User *UserInfo `json:"user_info"`
Token *TokenInfo `json:"token_info"`
ProjectsRoutes []*api.ProjectInitInfo `json:"projects"` // 项目路由
}
type GetUserInfoRsp = LoginRsp

View File

@ -0,0 +1,6 @@
package model
type IModel interface {
TableName() string
GetId() int
}

View File

@ -0,0 +1,43 @@
package model
import (
"admin/internal/db"
"admin/internal/errcode"
"admin/internal/global"
"errors"
"gorm.io/gorm"
"time"
)
func init() {
db.RegisterTableModels(Token{})
}
// Token token
type Token struct {
ID int `gorm:"primarykey" readonly:"true"`
Token string `gorm:"type:varchar(256);unique" required:"true"`
UserId int `name:"用户id" readonly:"true"`
ExpireAt time.Time `name:"到期时间" readonly:"true"`
CreatedAt time.Time `readonly:"true"`
}
func (m *Token) TableName() string {
return "token"
}
func (m *Token) GetId() int {
return m.ID
}
func (m *Token) GetByUserId(userId int) error {
err := global.GLOB_DB.Where("user_id=?", userId).First(m).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errcode.New(errcode.TokenInvalid, "user %v not found token", userId)
}
return errcode.New(errcode.TokenInvalid, "user %v found token error:%v", userId, err)
}
return nil
}

View File

@ -0,0 +1,52 @@
package model
import (
"admin/apps/user/model/dto"
"admin/internal/db"
"time"
)
func init() {
db.RegisterTableModels(User{})
}
// User 用户表
type User struct {
ID int `gorm:"primarykey" readonly:"true"`
UserName string `name:"账号名" gorm:"type:varchar(255);unique" required:"true" uneditable:"true"`
NickName string `name:"昵称" gorm:"type:varchar(255);unique" required:"true"`
UserPass string `gorm:"type:varchar(255)" name:"密码" required:"true"` // 用户密码hash后的值
CharacterName string `gorm:"type:varchar(255);" name:"角色组" choices:"GetCharChoices"`
Status int `gorm:"type:int(1);default:0;" choices:"GetStatusChoices"`
CreatedAt time.Time `readonly:"true"`
}
func (m *User) TableName() string {
return "user"
}
func (m *User) GetId() int {
return m.ID
}
func (u *User) GetStatusChoices() []*dto.CommonDtoFieldChoice {
return []*dto.CommonDtoFieldChoice{
{Desc: "正常", Value: 0},
{Desc: "停用", Value: 1},
}
}
func (u *User) GetCharChoices() []*dto.CommonDtoFieldChoice {
choices := make([]*dto.CommonDtoFieldChoice, 0)
choices = append(choices, &dto.CommonDtoFieldChoice{
Desc: "空",
Value: "",
})
for _, v := range new(Character).List() {
choices = append(choices, &dto.CommonDtoFieldChoice{
Desc: v.Name,
Value: v.Name,
})
}
return choices
}

View File

@ -0,0 +1,11 @@
package server
import "admin/apps/user/service"
type controller struct {
svc *service.Service
}
func newController(svc *service.Service) *controller {
return &controller{svc: svc}
}

View File

@ -0,0 +1,57 @@
package server
import (
"admin/apps/user/model/dto"
"admin/internal/context"
)
func (ctl *controller) CommonList(ctx *context.WebContext, params *dto.CommonListReq, rsp *dto.CommonListRsp) error {
resource := getCtxURIResource(ctx)
list, err := ctl.svc.CommonList(ctx, resource, params)
if err != nil {
return err
}
*rsp = *list
return nil
}
func (ctl *controller) CommonPost(ctx *context.WebContext, params *dto.CommonPostReq, rsp *dto.CommonPostRsp) error {
resource := getCtxURIResource(ctx)
newObj, err := ctl.svc.CommonPost(ctx, resource, *params.Dto)
if err != nil {
return err
}
rsp.Dto = &newObj
return nil
}
func (ctl *controller) CommonPut(ctx *context.WebContext, params *dto.CommonPutReq, rsp *dto.CommonPutRsp) error {
resource := getCtxURIResource(ctx)
err := ctl.svc.CommonPut(ctx, resource, *params.Dto)
if err != nil {
return err
}
rsp.Dto = params.Dto
return nil
}
func (ctl *controller) CommonDelete(ctx *context.WebContext, params *dto.CommonDeleteReq, rsp *dto.CommonDeleteRsp) error {
resource := getCtxURIResource(ctx)
err := ctl.svc.CommonDelete(ctx, resource, params.Id)
if err != nil {
return err
}
return nil
}
func (ctl *controller) OnClickCustomButton(ctx *context.WebContext, params *dto.CommonDeleteReq, rsp *dto.CommonDeleteRsp) error {
return nil
}
func getCtxURIResource(ctx *context.WebContext) string {
resource := ctx.GinCtx().Param("resource")
if resource == "" {
return "not_found_resource_in_url"
}
return resource
}

View File

@ -0,0 +1,24 @@
package server
import (
"admin/apps/user/model/dto"
"admin/internal/context"
)
func (ctl *controller) Login(ctx *context.WebContext, params *dto.LoginReq, rsp *dto.LoginRsp) error {
loginInfo, err := ctl.svc.Login(params.User, params.Password)
if err != nil {
return err
}
*rsp = *loginInfo
return nil
}
func (ctl *controller) GetUserInfo(ctx *context.WebContext, params *dto.NilReq, rsp *dto.GetUserInfoRsp) error {
svcRsp, err := ctl.svc.GetUserInfo(ctx.Header.UserId)
if err != nil {
return err
}
*rsp = *svcRsp
return nil
}

View File

@ -0,0 +1,26 @@
package server
import (
"admin/internal/context"
"admin/internal/errcode"
"strings"
)
func (srv *Server) CheckToken(ctx *context.WebContext) {
if strings.Contains(ctx.GinCtx().Request.URL.Path, "/login") {
return
}
err := ctx.ExtractHeader()
if err != nil {
ctx.Fail(errcode.New(errcode.HeaderParamsInvalid, "header invalid"))
return
}
err = srv.svc.CheckToken(ctx.Header.Token, ctx.Header.UserId)
if err != nil {
ctx.Fail(err)
ctx.GinCtx().Abort()
} else {
ctx.GinCtx().Next()
}
}

View File

@ -0,0 +1,28 @@
package server
import (
"admin/internal/consts"
"admin/lib/web"
)
func (srv *Server) Route(engine *web.Engine) {
engine.Use(srv.CheckToken)
apiGroup := engine.Group("/api", "")
{
userGroup := apiGroup.Group("/user", "用户操作组")
userGroup.Post("/login", "登录", consts.WebPathPermit_Write, srv.ctl.Login)
userGroup.Get("/info", "获取用户信息,里面包含用户权限信息,用于前端生成动态菜单", consts.WebPathPermit_Read, srv.ctl.GetUserInfo)
}
{
// 操作所有资源增删改查的接口
userResourceGroup := apiGroup.Group("/resource/:resource", "用户管理")
userResourceGroup.Get("", "查看列表", consts.WebPathPermit_Read, srv.ctl.CommonList)
userResourceGroup.Post("", "新增", consts.WebPathPermit_Read, srv.ctl.CommonPost)
userResourceGroup.Put("", "编辑", consts.WebPathPermit_Read, srv.ctl.CommonPut)
userResourceGroup.Delete("", "删除", consts.WebPathPermit_Read, srv.ctl.CommonDelete)
}
}

View File

@ -0,0 +1,15 @@
package server
import "admin/apps/user/service"
type Server struct {
svc *service.Service
ctl *controller
}
func New(svc *service.Service) *Server {
return &Server{
svc: svc,
ctl: newController(svc),
}
}

View File

@ -0,0 +1,57 @@
package service
import (
apiUser "admin/apps/user/api"
"admin/apps/user/domain"
"admin/apps/user/model/dto"
"admin/internal/context"
"gorm.io/gorm"
)
type Service struct {
db *gorm.DB
resourceSvc *domain.CommonResourceService
}
func New(db *gorm.DB) (*Service, error) {
svc := &Service{
db: db,
resourceSvc: domain.NewCommonResourceService(db),
}
err := svc.resourceSvc.EnsureInitDB()
if err != nil {
return nil, err
}
apiUser.RegisterUserApiHandler(svc)
return svc, nil
}
func (svc *Service) CommonList(ctx *context.WebContext, resourceName string, params *dto.CommonListReq) (*dto.CommonDtoList, error) {
var (
query string
args []any
)
fieldsDescInfo, rows, err := svc.resourceSvc.List(resourceName, params.PageNo, params.PageLen, query, args)
return &dto.CommonDtoList{FieldsDesc: fieldsDescInfo, Rows: rows}, err
}
func (svc *Service) CommonPost(ctx *context.WebContext, resourceName string, params dto.CommonDtoValues) (dto.CommonDtoValues, error) {
values, _, err := svc.resourceSvc.Create(resourceName, params)
if err != nil {
return nil, err
}
return values, err
}
func (svc *Service) CommonPut(ctx *context.WebContext, resourceName string, params dto.CommonDtoValues) error {
return svc.resourceSvc.Edit(resourceName, params)
}
func (svc *Service) CommonDelete(ctx *context.WebContext, resourceName string, id int) error {
return svc.resourceSvc.Delete(resourceName, id)
}
func (svc *Service) GetSupportResourcesList() []*dto.ResourceInitInfo {
return svc.resourceSvc.GetSupportResourcesList()
}

View File

@ -0,0 +1,107 @@
package service
import (
"admin/apps/game/api"
apiUser "admin/apps/user/api"
"admin/apps/user/domain/entity"
"admin/apps/user/model"
"admin/apps/user/model/dto"
"admin/internal/errcode"
"admin/lib/tokenlib"
"context"
)
func (svc *Service) CheckToken(token string, userId int) error {
if err := tokenlib.ValidToken(token, userId); err != nil {
return err
}
dbToken := new(model.Token)
err := dbToken.GetByUserId(userId)
if err != nil {
return err
}
if token != dbToken.Token {
return errcode.New(errcode.TokenInvalid, "token not equal:%v,%v", token, dbToken.Token)
}
return nil
}
func (svc *Service) Auth(ctx context.Context, req *apiUser.AuthReq) (*apiUser.AuthRsp, error) {
rsp := &apiUser.AuthRsp{}
err := svc.CheckToken(req.Token, req.UserId)
if err != nil {
return rsp, err
}
info, err := svc.GetUserInfo(req.UserId)
if err != nil {
return nil, err
}
rsp.User = &apiUser.UserInfo{
UserId: info.User.UserId,
NickName: info.User.NickName,
Icon: info.User.Icon,
Character: info.User.Character,
Permissions: info.User.Permissions,
}
rsp.Token = &apiUser.TokenInfo{
Token: info.Token.Token,
ExpireAt: info.Token.ExpireAt,
}
return rsp, nil
}
func (svc *Service) Login(user, pwd string) (info *dto.LoginRsp, err error) {
userInfo, err := svc.resourceSvc.Login(user, pwd)
if err != nil {
return nil, err
}
tokenInfo, err := svc.resourceSvc.GetOrCreateToken(userInfo.GetUserId())
if err != nil {
return nil, err
}
projectRoutesRsp, err := api.GetGameApiInstance().GetRoutes(&api.GetRoutesReq{IsAdmin: userInfo.IsAdmin(), Permissions: userInfo.GetPermissions()})
if err != nil {
return nil, err
}
info = &dto.LoginRsp{}
info.User = userInfo.ToDtoInfo()
info.Token = &dto.TokenInfo{Token: tokenInfo.Token, ExpireAt: tokenInfo.ExpireAt.Unix()}
info.ProjectsRoutes = projectRoutesRsp.Projects
return info, nil
}
func (svc *Service) GetUserInfo(userId int) (*dto.GetUserInfoRsp, error) {
user, find, err := svc.GetUserById(userId)
if err != nil {
return nil, err
}
if !find {
return nil, errcode.New(errcode.NotLogin, "not found user by id:%v", userId)
}
tokenInfo, err := svc.resourceSvc.GetToken(user.GetUserId())
if err != nil {
return nil, err
}
projectRoutesRsp, err := api.GetGameApiInstance().GetRoutes(&api.GetRoutesReq{IsAdmin: user.IsAdmin(), Permissions: user.GetPermissions()})
if err != nil {
return nil, err
}
rsp := &dto.GetUserInfoRsp{
User: user.ToDtoInfo(),
Token: &dto.TokenInfo{Token: tokenInfo.Token, ExpireAt: tokenInfo.ExpireAt.Unix()},
ProjectsRoutes: projectRoutesRsp.Projects,
}
return rsp, nil
}
func (svc *Service) GetUserById(userId int) (*entity.User, bool, error) {
return svc.resourceSvc.GetUserById(userId)
}

View File

@ -2,7 +2,7 @@ package main
import (
"admin/apps/game"
"admin/apps/mockpro"
"admin/apps/user"
"admin/internal/mynode"
"admin/lib/node"
)
@ -11,8 +11,9 @@ var appList []*node.ApplicationDescInfo
func init() {
appList = []*node.ApplicationDescInfo{
user.New(),
game.New(),
mockpro.New(),
//mockpro.New(),
}
}

View File

@ -6,6 +6,7 @@ require (
github.com/gin-contrib/pprof v1.5.3
github.com/gin-gonic/gin v1.10.0
github.com/go-sql-driver/mysql v1.7.0
github.com/golang-jwt/jwt/v5 v5.2.2
github.com/prometheus/client_golang v1.22.0
github.com/rs/zerolog v1.34.0
gopkg.in/yaml.v3 v3.0.1

View File

@ -36,6 +36,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=

View File

@ -18,6 +18,11 @@ var (
)
const (
// system
ResourcesName_SysUser = "user"
ResourcesName_SysCharacter = "character"
// game
ResourcesName_Project = "project"
ResourcesName_Server = "server"
ResourcesName_Account = "account"
@ -35,3 +40,12 @@ const (
WebPathPermit_Read = 1
WebPathPermit_Write = 2
)
const (
BanType_Account = "account"
BanType_Role = "role"
BanType_Ip = "ip"
BanType_AccountChat = "account_chat"
BanType_RoleChat = "role_chat"
BanType_Device = "device"
)

View File

@ -8,9 +8,16 @@ import (
"github.com/gin-gonic/gin"
)
type WebHeader struct {
UserId int `json:"UserId"` // 用户id会与下面token解析出用户id做匹配校验
Token string `json:"Token"` // jwt token内置一些信息
Ip string `json:"IP"`
}
type WebContext struct {
context.Context
rawCtx *gin.Context
Header *WebHeader
alreadySetRsp bool
}
@ -18,6 +25,20 @@ func NewWebContext(rawCtx *gin.Context) web.IContext {
return &WebContext{rawCtx: rawCtx}
}
func (ctx *WebContext) ExtractHeader() error {
header := &WebHeader{}
err := ctx.rawCtx.ShouldBindHeader(header)
if err != nil {
return err
}
ctx.Header = header
return nil
}
func (ctx *WebContext) IsUserLogin() bool {
return ctx.Header != nil && ctx.Header.UserId > 0
}
func (ctx *WebContext) GinCtx() *gin.Context {
return ctx.rawCtx
}

View File

@ -1,7 +1,12 @@
package errcode
const (
Ok = 0
ServerError = 1 // 服务器错误
DBError = 2 // 数据库错误
Ok = 0
ServerError = 1 // 服务器错误
DBError = 2 // 数据库错误
NotLogin = 3 // 没有登录,重定向到登录页面
HeaderParamsInvalid = 4
TokenInvalid = 5
UserOrPassInValid = 7 // 用户名或密码错误
NoPermission = 8 // 没有权限
)

View File

@ -0,0 +1,18 @@
package permission
import (
"fmt"
"strings"
)
func GetProjectResourcePermission(projectId int, resource string, method string) string {
return fmt.Sprintf("project:%v:%v:%v", projectId, resource, strings.ToLower(method))
}
func ParseProjectResourcePermission(permission string) (int, string, string, error) {
projectId := 0
resource := ""
method := ""
_, err := fmt.Scanf(permission, &projectId, resource, method)
return projectId, resource, method, err
}

View File

@ -0,0 +1,12 @@
package passlib
import "golang.org/x/crypto/bcrypt"
func EncryptPassword(password string) string {
binPwd, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(binPwd)
}
func ComparePassword(encryptPwd, plaintPwd string) bool {
return bcrypt.CompareHashAndPassword([]byte(encryptPwd), []byte(plaintPwd)) == nil
}

View File

@ -0,0 +1,56 @@
package tokenlib
import (
"admin/internal/errcode"
"fmt"
"github.com/golang-jwt/jwt/v5"
"strconv"
"time"
)
type RegisteredTokenClaims struct {
User string `json:"user,omitempty"`
jwt.RegisteredClaims
}
var (
tokenSign = []byte("token_sign") // token加盐
)
func ValidToken(token string, userId int) error {
checkToken, err := jwt.ParseWithClaims(token, &RegisteredTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
return tokenSign, nil
})
if err != nil {
return errcode.New(errcode.TokenInvalid, "ParseWithClaims token (%v) error:%v", token, err)
}
if !checkToken.Valid {
return errcode.New(errcode.TokenInvalid, "ParseWithClaims token (%v) invalid", token)
}
claims := checkToken.Claims.(*RegisteredTokenClaims)
if claims.User != strconv.Itoa(userId) {
return errcode.New(errcode.TokenInvalid, "token:%v extract user:%v not equal header give id:%v", token, claims.User, userId)
}
return nil
}
func GenToken(userId int, expire time.Duration) (string, error) {
user := strconv.Itoa(userId)
claims := &RegisteredTokenClaims{
User: user,
}
jwtClaims := jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(expire)),
Issuer: "test",
}
claims.RegisteredClaims = jwtClaims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
ss, err := token.SignedString(tokenSign)
if err != nil {
return "", fmt.Errorf("token SignedString error:%v", err)
}
return ss, nil
}

View File

@ -0,0 +1,20 @@
package tokenlib
import (
"testing"
"time"
)
func TestToken(t *testing.T) {
token, err := GenToken(123, time.Second*5)
if err != nil {
panic(err)
}
if err := ValidToken(token, 123); err != nil {
panic(err)
}
time.Sleep(time.Second * 6)
if err := ValidToken(token, 123); err == nil {
panic(token)
}
}

View File

@ -74,12 +74,23 @@ func getGinHandlerFunWithRequest(newContextFun func(ctx *gin.Context) IContext,
for _, handler := range handlers {
list = append(list, func(rawCtx *gin.Context) {
ctx := newContextFun(rawCtx)
customCtx, find := rawCtx.Get("custom_ctx")
if !find {
customCtx = newContextFun(rawCtx)
rawCtx.Set("custom_ctx", customCtx)
}
ctx := customCtx.(IContext)
handlerTo := reflect.TypeOf(handler)
numParams := handlerTo.NumIn()
if numParams != 3 {
ctx.HandleError(rawCtx.Request.RequestURI, fmt.Errorf("register callback handler params len(%v) invalid", numParams))
if numParams != 1 {
ctx.HandleError(rawCtx.Request.RequestURI, fmt.Errorf("register callback handler params len(%v) invalid", numParams))
return
}
// middleware
reflect.ValueOf(handler).Call([]reflect.Value{reflect.ValueOf(ctx)})
// 中间件内部已经走了响应数据打包了,这里直接返回
return
}

View File

@ -1,23 +0,0 @@
import request from '@/utils/request'
export function generateRoutes() {
return request({
url: "/routes",
method: "get"
})
}
export function gameApiRequest(url, method, data) {
if (method === "get") {
return request({
url: url,
method: method,
params: data
})
}
return request({
url: url,
method: method,
data: data
})
}

24
ui/src/api/sys.js Normal file
View File

@ -0,0 +1,24 @@
import request from '@/utils/request'
export function login(params) {
return request({
url: "/user/login",
method: "post",
data: params
})
}
export function getUserInfo(params) {
return request({
url: "/user/info",
method: "get",
data: params
})
}
export function generateRoutes() {
return request({
url: "/routes",
method: "get"
})
}

View File

@ -0,0 +1,67 @@
<svg width="100%" height="100%" id="svg" viewBox="0 0 1440 400" xmlns="http://www.w3.org/2000/svg" class="transition duration-300 ease-in-out delay-150"><style>
.path-0{
animation:pathAnim-0 4s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes pathAnim-0{
0%{
d: path("M 0,400 C 0,400 0,100 0,100 C 50.8103747173373,109.20471951536959 101.6207494346746,118.4094390307392 143,110 C 184.3792505653254,101.5905609692608 216.32737697863888,75.56696339241286 262,74 C 307.6726230213611,72.43303660758714 367.0697426507697,95.32270739960937 415,100 C 462.9302573492303,104.67729260039063 499.3936524182825,91.14220700914969 539,86 C 578.6063475817175,80.85779299085031 621.3556476761007,84.1084645637919 664,85 C 706.6443523238993,85.8915354362081 749.1837568773142,84.42393473568269 789,75 C 828.8162431226858,65.57606526431731 865.9093248146424,48.19579649347729 914,66 C 962.0906751853576,83.80420350652271 1021.1789438641165,136.79287929040817 1067,137 C 1112.8210561358835,137.20712070959183 1145.3748997288917,84.63268634489012 1179,69 C 1212.6251002711083,53.36731365510988 1247.3214572203167,74.67637533003139 1291,86 C 1334.6785427796833,97.32362466996861 1387.3392713898415,98.6618123349843 1440,100 C 1440,100 1440,400 1440,400 Z");
}
25%{
d: path("M 0,400 C 0,400 0,100 0,100 C 38.085950711769925,108.68663884161857 76.17190142353985,117.37327768323715 122,109 C 167.82809857646015,100.62672231676285 221.3983450176105,75.19352810866998 267,81 C 312.6016549823895,86.80647189133002 350.2347185060181,123.85260988208296 390,128 C 429.7652814939819,132.14739011791704 471.66278095831717,103.39603236299823 522,94 C 572.3372190416828,84.60396763700177 631.1141576607127,94.5632606659241 675,109 C 718.8858423392873,123.4367393340759 747.880588398832,142.35092497330533 791,132 C 834.119411601168,121.64907502669469 891.3634887439594,82.03303944085465 931,79 C 970.6365112560406,75.96696055914535 992.6654566253305,109.51691726327608 1035,108 C 1077.3345433746695,106.48308273672392 1139.974684754719,69.89929150604104 1189,66 C 1238.025315245281,62.100708493958955 1273.4358043557945,90.8859167125597 1313,102 C 1352.5641956442055,113.1140832874403 1396.2820978221027,106.55704164372014 1440,100 C 1440,100 1440,400 1440,400 Z");
}
50%{
d: path("M 0,400 C 0,400 0,100 0,100 C 37.01395207173833,109.69011844829022 74.02790414347666,119.38023689658044 118,124 C 161.97209585652334,128.61976310341956 212.90233549783164,128.16917086196852 257,118 C 301.09766450216836,107.83082913803148 338.3627538651966,87.94307965554552 379,89 C 419.6372461348034,90.05692034445448 463.6466490413816,112.05851051584946 506,126 C 548.3533509586184,139.94148948415054 589.0506499692768,145.8228782810566 636,128 C 682.9493500307232,110.1771217189434 736.1507510815113,68.64997635992414 788,67 C 839.8492489184887,65.35002364007586 890.3463457046779,103.57721627924685 928,111 C 965.6536542953221,118.42278372075315 990.4638660997771,95.04115852308847 1038,95 C 1085.536133900223,94.95884147691153 1155.798189896214,118.25814962839925 1199,128 C 1242.201810103786,137.74185037160075 1258.3433743153673,133.9262429633145 1294,127 C 1329.6566256846327,120.0737570366855 1384.8283128423163,110.03687851834275 1440,100 C 1440,100 1440,400 1440,400 Z");
}
75%{
d: path("M 0,400 C 0,400 0,100 0,100 C 45.604500645453754,100.93447183466468 91.20900129090751,101.86894366932935 134,96 C 176.7909987090925,90.13105633067065 216.76849548182372,77.45869715734729 262,80 C 307.2315045181763,82.54130284265271 357.7170167817977,100.29626770128152 403,97 C 448.2829832182023,93.70373229871848 488.3634373909856,69.35623203752661 522,63 C 555.6365626090144,56.64376796247338 582.82923365426,68.27880414861198 634,70 C 685.17076634574,71.72119585138802 760.3196279919748,63.528551368025504 809,76 C 857.6803720080252,88.4714486319745 879.8922543778411,121.606990379286 917,133 C 954.1077456221589,144.393009620714 1006.1113544966613,134.04348711483055 1052,117 C 1097.8886455033387,99.95651288516945 1137.6623276355142,76.21906116139176 1177,73 C 1216.3376723644858,69.78093883860824 1255.2393349612817,87.08026823960235 1299,95 C 1342.7606650387183,102.91973176039765 1391.3803325193592,101.45986588019883 1440,100 C 1440,100 1440,400 1440,400 Z");
}
100%{
d: path("M 0,400 C 0,400 0,100 0,100 C 50.8103747173373,109.20471951536959 101.6207494346746,118.4094390307392 143,110 C 184.3792505653254,101.5905609692608 216.32737697863888,75.56696339241286 262,74 C 307.6726230213611,72.43303660758714 367.0697426507697,95.32270739960937 415,100 C 462.9302573492303,104.67729260039063 499.3936524182825,91.14220700914969 539,86 C 578.6063475817175,80.85779299085031 621.3556476761007,84.1084645637919 664,85 C 706.6443523238993,85.8915354362081 749.1837568773142,84.42393473568269 789,75 C 828.8162431226858,65.57606526431731 865.9093248146424,48.19579649347729 914,66 C 962.0906751853576,83.80420350652271 1021.1789438641165,136.79287929040817 1067,137 C 1112.8210561358835,137.20712070959183 1145.3748997288917,84.63268634489012 1179,69 C 1212.6251002711083,53.36731365510988 1247.3214572203167,74.67637533003139 1291,86 C 1334.6785427796833,97.32362466996861 1387.3392713898415,98.6618123349843 1440,100 C 1440,100 1440,400 1440,400 Z");
}
}</style><defs><linearGradient id="gradient" x1="4%" y1="70%" x2="96%" y2="30%"><stop offset="5%" stop-color="#8ed1fc"></stop><stop offset="95%" stop-color="#32ded4"></stop></linearGradient></defs><path d="M 0,400 C 0,400 0,100 0,100 C 50.8103747173373,109.20471951536959 101.6207494346746,118.4094390307392 143,110 C 184.3792505653254,101.5905609692608 216.32737697863888,75.56696339241286 262,74 C 307.6726230213611,72.43303660758714 367.0697426507697,95.32270739960937 415,100 C 462.9302573492303,104.67729260039063 499.3936524182825,91.14220700914969 539,86 C 578.6063475817175,80.85779299085031 621.3556476761007,84.1084645637919 664,85 C 706.6443523238993,85.8915354362081 749.1837568773142,84.42393473568269 789,75 C 828.8162431226858,65.57606526431731 865.9093248146424,48.19579649347729 914,66 C 962.0906751853576,83.80420350652271 1021.1789438641165,136.79287929040817 1067,137 C 1112.8210561358835,137.20712070959183 1145.3748997288917,84.63268634489012 1179,69 C 1212.6251002711083,53.36731365510988 1247.3214572203167,74.67637533003139 1291,86 C 1334.6785427796833,97.32362466996861 1387.3392713898415,98.6618123349843 1440,100 C 1440,100 1440,400 1440,400 Z" stroke="none" stroke-width="0" fill="url(#gradient)" fill-opacity="0.4" class="transition-all duration-300 ease-in-out delay-150 path-0"></path><style>
.path-1{
animation:pathAnim-1 4s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes pathAnim-1{
0%{
d: path("M 0,400 C 0,400 0,200 0,200 C 33.037747977312605,204.17407595636058 66.07549595462521,208.3481519127212 114,202 C 161.9245040453748,195.6518480872788 224.73576415881178,178.78146830547584 277,172 C 329.2642358411882,165.21853169452416 370.98144741012777,168.5259748653755 407,172 C 443.01855258987223,175.4740251346245 473.3384462006769,179.1146322330221 516,189 C 558.6615537993231,198.8853677669779 613.6647677871649,215.01549620253616 666,207 C 718.3352322128351,198.98450379746384 768.0024826506634,166.8233829568334 801,163 C 833.9975173493366,159.1766170431666 850.3253016101814,183.69097197013028 892,189 C 933.6746983898186,194.30902802986972 1000.6963109086112,180.41272916264552 1056,174 C 1111.3036890913888,167.58727083735448 1154.8894547553741,168.65811137928773 1197,175 C 1239.1105452446259,181.34188862071227 1279.745870069893,192.9548253202035 1320,198 C 1360.254129930107,203.0451746797965 1400.1270649650535,201.52258733989825 1440,200 C 1440,200 1440,400 1440,400 Z");
}
25%{
d: path("M 0,400 C 0,400 0,200 0,200 C 50.144999548448,205.21959416984376 100.289999096896,210.43918833968752 145,205 C 189.710000903104,199.56081166031248 228.98500316086404,183.46284081109366 270,183 C 311.01499683913596,182.53715918890634 353.76998825964773,197.70944841593783 395,201 C 436.23001174035227,204.29055158406217 475.93504380054515,195.699365525155 521,193 C 566.0649561994549,190.300634474845 616.489836538172,193.49308948344222 657,190 C 697.510163461828,186.50691051655778 728.1056100467666,176.32827654107615 778,184 C 827.8943899532334,191.67172345892385 897.0877232747614,217.1938043522531 938,223 C 978.9122767252386,228.8061956477469 991.5434968541877,214.89650604991155 1031,213 C 1070.4565031458123,211.10349395008845 1136.738289308488,221.22017144810076 1183,218 C 1229.261710691512,214.77982855189924 1255.5033459118606,198.2228081576855 1295,193 C 1334.4966540881394,187.7771918423145 1387.2483270440698,193.88859592115725 1440,200 C 1440,200 1440,400 1440,400 Z");
}
50%{
d: path("M 0,400 C 0,400 0,200 0,200 C 36.84512474345648,198.16914961246212 73.69024948691296,196.33829922492424 119,194 C 164.30975051308704,191.66170077507576 218.08412679580465,188.81595271276512 264,200 C 309.91587320419535,211.18404728723488 347.97324332986847,236.39788992401526 385,236 C 422.02675667013153,235.60211007598474 458.02289988472137,209.5924875911737 504,198 C 549.9771001152786,186.4075124088263 605.9351571312458,189.23215971129002 656,203 C 706.0648428687542,216.76784028870998 750.2364715902953,241.47887356366618 794,228 C 837.7635284097047,214.52112643633382 881.1189565075728,162.85234603404527 919,166 C 956.8810434924272,169.14765396595473 989.2877023794135,227.1117423001528 1038,233 C 1086.7122976205865,238.8882576998472 1151.730233974773,192.70068476534348 1196,180 C 1240.269766025227,167.29931523465652 1263.7913617214933,188.0855186384733 1301,197 C 1338.2086382785067,205.9144813615267 1389.1043191392532,202.95724068076333 1440,200 C 1440,200 1440,400 1440,400 Z");
}
75%{
d: path("M 0,400 C 0,400 0,200 0,200 C 52.74494394557294,199.86307526398085 105.48988789114588,199.7261505279617 149,193 C 192.51011210885412,186.2738494720383 226.78539238098944,172.95847315213408 271,185 C 315.21460761901056,197.04152684786592 369.3685425848962,234.43995686350206 413,235 C 456.6314574151038,235.56004313649794 489.7404372794257,199.28169939385782 527,183 C 564.2595627205743,166.71830060614218 605.669708297401,170.43324556106666 646,178 C 686.330291702599,185.56675443893334 725.5807295309703,196.98531836187553 773,211 C 820.4192704690297,225.01468163812447 876.0073735787179,241.62548099143115 926,229 C 975.9926264212821,216.37451900856885 1020.3897761541582,174.51275767239986 1059,173 C 1097.6102238458418,171.48724232760014 1130.4335218046497,210.32348831896928 1170,227 C 1209.5664781953503,243.67651168103072 1255.8761366272429,238.19328905172307 1302,230 C 1348.1238633727571,221.80671094827693 1394.0619316863786,210.90335547413846 1440,200 C 1440,200 1440,400 1440,400 Z");
}
100%{
d: path("M 0,400 C 0,400 0,200 0,200 C 33.037747977312605,204.17407595636058 66.07549595462521,208.3481519127212 114,202 C 161.9245040453748,195.6518480872788 224.73576415881178,178.78146830547584 277,172 C 329.2642358411882,165.21853169452416 370.98144741012777,168.5259748653755 407,172 C 443.01855258987223,175.4740251346245 473.3384462006769,179.1146322330221 516,189 C 558.6615537993231,198.8853677669779 613.6647677871649,215.01549620253616 666,207 C 718.3352322128351,198.98450379746384 768.0024826506634,166.8233829568334 801,163 C 833.9975173493366,159.1766170431666 850.3253016101814,183.69097197013028 892,189 C 933.6746983898186,194.30902802986972 1000.6963109086112,180.41272916264552 1056,174 C 1111.3036890913888,167.58727083735448 1154.8894547553741,168.65811137928773 1197,175 C 1239.1105452446259,181.34188862071227 1279.745870069893,192.9548253202035 1320,198 C 1360.254129930107,203.0451746797965 1400.1270649650535,201.52258733989825 1440,200 C 1440,200 1440,400 1440,400 Z");
}
}</style><defs><linearGradient id="gradient" x1="4%" y1="70%" x2="96%" y2="30%"><stop offset="5%" stop-color="#8ed1fc"></stop><stop offset="95%" stop-color="#32ded4"></stop></linearGradient></defs><path d="M 0,400 C 0,400 0,200 0,200 C 33.037747977312605,204.17407595636058 66.07549595462521,208.3481519127212 114,202 C 161.9245040453748,195.6518480872788 224.73576415881178,178.78146830547584 277,172 C 329.2642358411882,165.21853169452416 370.98144741012777,168.5259748653755 407,172 C 443.01855258987223,175.4740251346245 473.3384462006769,179.1146322330221 516,189 C 558.6615537993231,198.8853677669779 613.6647677871649,215.01549620253616 666,207 C 718.3352322128351,198.98450379746384 768.0024826506634,166.8233829568334 801,163 C 833.9975173493366,159.1766170431666 850.3253016101814,183.69097197013028 892,189 C 933.6746983898186,194.30902802986972 1000.6963109086112,180.41272916264552 1056,174 C 1111.3036890913888,167.58727083735448 1154.8894547553741,168.65811137928773 1197,175 C 1239.1105452446259,181.34188862071227 1279.745870069893,192.9548253202035 1320,198 C 1360.254129930107,203.0451746797965 1400.1270649650535,201.52258733989825 1440,200 C 1440,200 1440,400 1440,400 Z" stroke="none" stroke-width="0" fill="url(#gradient)" fill-opacity="0.53" class="transition-all duration-300 ease-in-out delay-150 path-1"></path><style>
.path-2{
animation:pathAnim-2 4s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes pathAnim-2{
0%{
d: path("M 0,400 C 0,400 0,300 0,300 C 51.227644191181795,301.9763528409705 102.45528838236359,303.952705681941 145,297 C 187.5447116176364,290.047294318059 221.4064906617274,274.16553011320667 262,285 C 302.5935093382726,295.83446988679333 349.91874897072705,333.38517386523216 398,331 C 446.08125102927295,328.61482613476784 494.9185134553645,286.29377442586485 537,283 C 579.0814865446355,279.70622557413515 614.4071972078149,315.4397284313083 656,328 C 697.5928027921851,340.5602715686917 745.452697713376,329.94731184890185 792,315 C 838.547302286624,300.05268815109815 883.782011938681,280.77102417308436 929,279 C 974.217988061319,277.22897582691564 1019.4192545318997,292.9685914587609 1058,289 C 1096.5807454681003,285.0314085412391 1128.5409699337195,261.35460999187205 1174,264 C 1219.4590300662805,266.64539000812795 1278.416865733223,295.61296857375083 1325,306 C 1371.583134266777,316.38703142624917 1405.7915671333885,308.1935157131246 1440,300 C 1440,300 1440,400 1440,400 Z");
}
25%{
d: path("M 0,400 C 0,400 0,300 0,300 C 54.53282428960243,293.6367148971435 109.06564857920486,287.2734297942871 148,277 C 186.93435142079514,266.7265702057129 210.27022997278294,252.54299571999525 247,264 C 283.72977002721706,275.45700428000475 333.8534315296634,312.5545873257319 378,320 C 422.1465684703366,327.4454126742681 460.3160439085634,305.2386549770771 509,294 C 557.6839560914366,282.7613450229229 616.8823928360831,282.4907927659598 664,290 C 711.1176071639169,297.5092072340402 746.1543847471042,312.798173959084 787,319 C 827.8456152528958,325.201826040916 874.5000681754998,322.31651139770406 924,313 C 973.4999318245002,303.68348860229594 1025.8453425508972,287.93578045009997 1062,281 C 1098.1546574491028,274.06421954990003 1118.1185616209123,275.9403668018961 1160,286 C 1201.8814383790877,296.0596331981039 1265.6804109654536,314.3027523423154 1316,318 C 1366.3195890345464,321.6972476576846 1403.1597945172732,310.8486238288423 1440,300 C 1440,300 1440,400 1440,400 Z");
}
50%{
d: path("M 0,400 C 0,400 0,300 0,300 C 32.00253223284501,282.3792505653254 64.00506446569003,264.7585011306508 106,273 C 147.99493553430997,281.2414988693492 199.98227437008495,315.3452460427221 249,317 C 298.01772562991505,318.6547539572779 344.0658380539702,287.8605146984607 386,284 C 427.9341619460298,280.1394853015393 465.75437341403415,303.2126951634353 508,307 C 550.2456265859659,310.7873048365647 596.9166682898929,295.2887046477983 642,297 C 687.0833317101071,298.7112953522017 730.5789534263944,317.6324862453716 784,310 C 837.4210465736056,302.3675137546284 900.7675180045297,268.1813503707154 939,267 C 977.2324819954703,265.8186496292846 990.3509745554868,297.64211227176696 1030,315 C 1069.6490254445132,332.35788772823304 1135.8285837735227,335.2502005422166 1185,320 C 1234.1714162264773,304.7497994577834 1266.334690350422,271.3570855593667 1306,265 C 1345.665309649578,258.6429144406333 1392.832654824789,279.3214572203167 1440,300 C 1440,300 1440,400 1440,400 Z");
}
75%{
d: path("M 0,400 C 0,400 0,300 0,300 C 54.93754592992266,285.7678031020738 109.87509185984533,271.53560620414754 149,274 C 188.12490814015467,276.46439379585246 211.43717849054133,295.6253782854836 252,301 C 292.5628215094587,306.3746217145164 350.3761941779894,297.96288065391815 393,306 C 435.6238058220106,314.03711934608185 463.058044797501,338.52309909884383 502,336 C 540.941955202499,333.47690090115617 591.3916266320064,303.9447229507065 636,295 C 680.6083733679936,286.0552770492935 719.3754486744734,297.69800909833026 766,307 C 812.6245513255266,316.30199090166974 867.1065786700998,323.26324065597225 921,309 C 974.8934213299002,294.73675934402775 1028.1982366451277,259.24902827778067 1071,267 C 1113.8017633548723,274.75097172221933 1146.1004747493887,325.74064623290525 1185,328 C 1223.8995252506113,330.25935376709475 1269.3998643573175,283.7883867905985 1313,271 C 1356.6001356426825,258.2116132094015 1398.3000678213411,279.1058066047008 1440,300 C 1440,300 1440,400 1440,400 Z");
}
100%{
d: path("M 0,400 C 0,400 0,300 0,300 C 51.227644191181795,301.9763528409705 102.45528838236359,303.952705681941 145,297 C 187.5447116176364,290.047294318059 221.4064906617274,274.16553011320667 262,285 C 302.5935093382726,295.83446988679333 349.91874897072705,333.38517386523216 398,331 C 446.08125102927295,328.61482613476784 494.9185134553645,286.29377442586485 537,283 C 579.0814865446355,279.70622557413515 614.4071972078149,315.4397284313083 656,328 C 697.5928027921851,340.5602715686917 745.452697713376,329.94731184890185 792,315 C 838.547302286624,300.05268815109815 883.782011938681,280.77102417308436 929,279 C 974.217988061319,277.22897582691564 1019.4192545318997,292.9685914587609 1058,289 C 1096.5807454681003,285.0314085412391 1128.5409699337195,261.35460999187205 1174,264 C 1219.4590300662805,266.64539000812795 1278.416865733223,295.61296857375083 1325,306 C 1371.583134266777,316.38703142624917 1405.7915671333885,308.1935157131246 1440,300 C 1440,300 1440,400 1440,400 Z");
}
}</style><defs><linearGradient id="gradient" x1="4%" y1="70%" x2="96%" y2="30%"><stop offset="5%" stop-color="#8ed1fc"></stop><stop offset="95%" stop-color="#32ded4"></stop></linearGradient></defs><path d="M 0,400 C 0,400 0,300 0,300 C 51.227644191181795,301.9763528409705 102.45528838236359,303.952705681941 145,297 C 187.5447116176364,290.047294318059 221.4064906617274,274.16553011320667 262,285 C 302.5935093382726,295.83446988679333 349.91874897072705,333.38517386523216 398,331 C 446.08125102927295,328.61482613476784 494.9185134553645,286.29377442586485 537,283 C 579.0814865446355,279.70622557413515 614.4071972078149,315.4397284313083 656,328 C 697.5928027921851,340.5602715686917 745.452697713376,329.94731184890185 792,315 C 838.547302286624,300.05268815109815 883.782011938681,280.77102417308436 929,279 C 974.217988061319,277.22897582691564 1019.4192545318997,292.9685914587609 1058,289 C 1096.5807454681003,285.0314085412391 1128.5409699337195,261.35460999187205 1174,264 C 1219.4590300662805,266.64539000812795 1278.416865733223,295.61296857375083 1325,306 C 1371.583134266777,316.38703142624917 1405.7915671333885,308.1935157131246 1440,300 C 1440,300 1440,400 1440,400 Z" stroke="none" stroke-width="0" fill="url(#gradient)" fill-opacity="1" class="transition-all duration-300 ease-in-out delay-150 path-2"></path></svg>

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -0,0 +1,11 @@
<script setup>
</script>
<template>
<el-empty description="没有权限!请联系管理员添加权限!"></el-empty>
</template>
<style scoped>
</style>

View File

@ -3,18 +3,24 @@ import {ElNotification} from "element-plus";
import {resourceDelete, resourceList, resourcePost, resourcePut} from "@/api/resource.js";
import {ref, toRaw} from "vue";
import {useRoute} from 'vue-router';
import LocalCache from "@/stores/project.js";
import LocalCache from "@/stores/localCache.js";
import empty from '@/components/restful/empty.vue';
const cachedResource = LocalCache.getCache("resource");
const listRsp = ref({fields_desc: [], rows: []})
const listDataOK = ref(false)
const resource_raw_node = cachedResource;
const hasListPermit = resource_raw_node.meta.methods.get !== undefined && resource_raw_node.meta.methods.get === true;
const resource_url = cachedResource.meta.resource_url;
const fieldsDescInfo = ref([])
const rows = ref([])
const rules = ref({})
const current_page = ref(0)
const page_size = ref(0)
const item = ref({
id: 0,
number: 1,
@ -205,168 +211,102 @@ const handleCloseDialog = () => {
</script>
<template>
<el-container v-if="listDataOK">
<el-header>
<el-button @click="dialogAddVisible = true" size="large" type="primary"
v-if="(resource_raw_node.meta.methods.post === true)">
添加
</el-button>
</el-header>
<el-main>
<el-table :data="rows" style="width: 100%" table-layout="auto" stripe>
<template v-for="fieldDescInfo in fieldsDescInfo">
<el-table-column prop="jsonValue" :label="fieldDescInfo.name"
v-if="(fieldDescInfo.type === 'items')"></el-table-column>
<el-table-column :prop="fieldDescInfo.key" :label="fieldDescInfo.name"
v-else></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)"
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)"
v-if="(resource_raw_node.meta.methods.delete === true)">
<el-icon style="vertical-align: middle">
<Delete/>
</el-icon>
<span>删除</span>
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="dialogAddVisible" :mask="true" title="添加" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogAddFormRef" :model="dialogAddForm" :rules="rules" label-position="right"
label-width="130px">
<template v-if="!hasListPermit">
<component :is="empty"></component>
</template>
<template v-else>
<el-container v-if="listDataOK">
<el-header>
<el-button @click="dialogAddVisible = true" size="large" type="primary"
v-if="(resource_raw_node.meta.methods.post === true)">
添加
</el-button>
</el-header>
<el-main>
<el-table :data="rows" style="width: 100%" table-layout="auto" stripe>
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如何是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" 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 placeholder="--选择道具后填数量点击添加--" v-model="item.id" style="width: 150px"
filterable>
<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>
<el-form-item label="数量" prop="num">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
</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="Attach">
<el-table :data="dialogAddForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</template>
<template v-else-if="(fieldDescInfo.readonly !== true)">
<!-- 有可选项的字段走下拉框或者多选框 -->
<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 ? '--多选--' : '--单选--')"
v-model="dialogAddForm[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="dialogAddForm[fieldDescInfo.key]" type="datetime"
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="dialogAddForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text"></el-input>
</el-form-item>
</template>
</template>
<el-table-column prop="jsonValue" :label="fieldDescInfo.name"
v-if="(fieldDescInfo.type === 'items')"></el-table-column>
<el-table-column :prop="fieldDescInfo.key" :label="fieldDescInfo.name"
v-else></el-table-column>
</template>
<el-table-column prop="func" label="功 能">
<template #default="scope">
<el-form-item>
<el-button @click="submitAdd(dialogAddFormRef)" size="large" type="primary">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
<el-dialog v-model="dialogEditVisible" :mask="true" title="编辑" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogEditFormRef" :model="dialogEditForm" :rules="rules" class="operation_form"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" 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.id" style="width: 150px"
filterable>
<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>
<el-form-item label="数量" prop="number">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
</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-table :data="dialogEditForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
<el-button size="default" 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)"
v-if="(resource_raw_node.meta.methods.delete === true)">
<el-icon style="vertical-align: middle">
<Delete/>
</el-icon>
<span>删除</span>
</el-button>
</template>
</el-table-column>
</el-table>
<template v-else-if="(fieldDescInfo.readonly !== true)">
<template v-if="(fieldDescInfo.uneditable !== true)">
<!-- 表格数据分页 -->
<div class="demo-pagination-block">
<div class="demonstration"></div>
<el-pagination
v-model:current-page="current_page"
v-model:page-size="page_size"
:page-sizes="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
/>
</div>
<el-dialog v-model="dialogAddVisible" :mask="true" title="添加" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogAddFormRef" :model="dialogAddForm" :rules="rules" label-position="right"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如何是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" 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 placeholder="--选择道具后填数量点击添加--" v-model="item.id" style="width: 150px"
filterable>
<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>
<el-form-item label="数量" prop="num">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
</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="Attach">
<el-table :data="dialogAddForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</template>
<template v-else-if="(fieldDescInfo.readonly !== true)">
<!-- 有可选项的字段走下拉框或者多选框 -->
<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 ? '--多选--' : '--单选--')"
v-model="dialogEditForm[fieldDescInfo.key]" style="width: 150px"
v-model="dialogAddForm[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>
@ -378,7 +318,7 @@ const handleCloseDialog = () => {
<!-- 时间戳字段展示时间选择器 -->
<template v-else-if="(fieldDescInfo.type === 'Time')">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-date-picker v-model="dialogEditForm[fieldDescInfo.key]" type="datetime"
<el-date-picker v-model="dialogAddForm[fieldDescInfo.key]" type="datetime"
placeholder="选个时间" format="YYYY/MM/DD HH:mm:ss"
value-format="YYYY/MM/DD HH:mm:ss"></el-date-picker>
</el-form-item>
@ -387,36 +327,127 @@ const handleCloseDialog = () => {
<!-- 否则就是普通字段 -->
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogEditForm[fieldDescInfo.key]"
<el-input v-model="dialogAddForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text"></el-input>
</el-form-item>
</template>
</template>
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogEditForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text" disabled></el-input>
</template>
<el-form-item>
<el-button @click="submitAdd(dialogAddFormRef)" size="large" type="primary">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
<el-dialog v-model="dialogEditVisible" :mask="true" title="编辑" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogEditFormRef" :model="dialogEditForm" :rules="rules" class="operation_form"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" 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.id" style="width: 150px"
filterable>
<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>
<el-form-item label="数量" prop="number">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
</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-table :data="dialogEditForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</template>
<template v-else-if="(fieldDescInfo.readonly !== true)">
<template v-if="(fieldDescInfo.uneditable !== true)">
<!-- 有可选项的字段走下拉框或者多选框 -->
<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 ? '--多选--' : '--单选--')"
v-model="dialogEditForm[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="dialogEditForm[fieldDescInfo.key]" type="datetime"
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="dialogEditForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text"></el-input>
</el-form-item>
</template>
</template>
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogEditForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text" 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-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">-->
<!-- <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>
<el-form-item>
<el-button @click="submitEdit(dialogEditFormRef)" size="large" type="primary">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</el-main>
</el-container>
</el-main>
</el-container>
</template>
</template>
<style scoped lang="scss">
.demo-pagination-block {
margin-top: 10px;
.el-pagination {
right: 40px;
position: absolute;
}
}
</style>

View File

@ -0,0 +1,551 @@
<script setup>
import {ElNotification} from "element-plus";
import {resourceDelete, resourceList, resourcePost, resourcePut} from "@/api/resource.js";
import {ref, toRaw} from "vue";
import {useRoute} from 'vue-router';
import LocalCache from "@/stores/localCache.js";
import {getPermissionTreeByProjects, getSelectedPermissions} from "@/utils/permission.js";
import empty from '@/components/restful/empty.vue';
const cachedResource = LocalCache.getCache("resource");
const listRsp = ref({fields_desc: [], rows: []})
const listDataOK = ref(false)
const resource_raw_node = cachedResource;
const hasListPermit = resource_raw_node.meta.methods.get !== undefined && resource_raw_node.meta.methods.get === true;
const resource_url = cachedResource.meta.resource_url;
const fieldsDescInfo = ref([])
const rows = ref([])
const rules = ref({})
const current_page = ref(0)
const page_size = ref(0)
const item = ref({
id: 0,
number: 1,
})
// console.log("enter table, resource:", cachedResource)
const projectsRoute = LocalCache.getCache("projectsRoute")
const permitTreeDefaultProps = {children: 'children', label: 'label'}
const permitTree = ref([])
const selectedPermitList = ref([])
const listData = async () => {
try {
let listParams = {
page_no: 0,
page_len: 100,
}
// console.log("list params:", listParams)
const rspData = await resourceList(resource_url, listParams);
listRsp.value = rspData;
if (listRsp.value.code !== 200) throw new Error("请求失败,错误码:", listRsp.code);
fieldsDescInfo.value = listRsp.value.data.fields_desc
rows.value = listRsp.value.data.rows
for (let i = 0; i < fieldsDescInfo.value.length; i++) {
var field = fieldsDescInfo.value[i]
dialogAddForm.value[field.key] = ''
if (field.required == true) {
rules.value[field.key] = [{required: true, message: field.name + "不能为空", trigger: "blur"}]
}
if (field.type == "items") {
dialogAddForm.value[field.key] = []
for (let j = 0; j < rows.value.length; j++) {
rows.value[j].jsonValue = JSON.stringify(rows.value[j][field.key])
}
} else if (field.key == "Permissions") {
// console.log("", projectsRoute)
const permitNodeList = getPermissionTreeByProjects(projectsRoute)
// console.log("", permitNodeList)
permitTree.value.push(...permitNodeList)
// console.log("", permitTree.value)
} else if (field.key == 'UserPass') {
for (let j = 0; j < rows.value.length; j++) {
rows.value[j].jsonValue = "***"
}
}
// console.log("", field)
}
// console.log('await list rsp:', listRsp.value, fieldsDescInfo.value, toRaw(rows.value), toRaw(rules.value))
listDataOK.value = true
} catch (err) {
console.log(err)
} finally {
}
}
onMounted(() => {
if (hasListPermit) {
listData();
}
})
const dialogAddVisible = ref(false)
const dialogEditVisible = ref(false)
const dialogAddFormRef = ref(null)
const dialogEditFormRef = ref(null)
const dialogAddForm = ref({
ServerIDs: [],
Attach: [],
Permissions: [],
})
const dialogEditForm = ref({})
const submitAdd = async () => {
try {
await dialogAddFormRef.value.validate(valid => {
if (valid) {
dialogAddForm.value.Permissions = selectedPermitList.value
console.log("commit add form:", dialogAddForm.value)
resourcePost(resource_url, dialogAddForm.value).then((res) => {
ElNotification({
title: "添加结果通知",
message: "添加成功!",
type: 'success',
duration: 4000,
"show-close": true,
})
rows.value.push(res.data.dto)
dialogAddVisible.value = false
handleCloseDialog()
}, (err) => {
console.log("添加报错:", err)
})
console.log("提交数据:", dialogAddForm.value)
}
})
} catch (error) {
console.log("校验失败:", error)
}
}
const submitEdit = async () => {
try {
await dialogEditFormRef.value.validate(valid => {
if (valid) {
dialogEditForm.value.Permissions = selectedPermitList.value
resourcePut(resource_url, dialogEditForm.value).then((res) => {
ElNotification({
title: "编辑结果通知",
message: "编辑成功!",
type: 'success',
duration: 4000,
"show-close": true,
})
dialogEditVisible.value = false
rows.value[dialogEditForm.value.oldIndex] = res.data.dto
handleCloseDialog()
}, (err) => {
console.log("添加报错:", err)
})
console.log("提交数据:", dialogEditForm.value)
}
})
} catch (error) {
console.log("校验失败:", error)
}
}
const handleEdit = (index, row) => {
dialogEditForm.value.oldData = row
dialogEditForm.value.oldIndex = index
dialogEditForm.value = row
console.log("edit data:", row)
dialogEditVisible.value = true
if (permitTree.value.length != 0) {
selectedPermitList.value = getSelectedPermissions(permitTree.value, row.Permissions)
}
}
const handleDelete = (index, row) => {
ElMessageBox.confirm("确定要删除吗?").then(() => {
resourceDelete(resource_url, {id: row.ID}).then((res) => {
ElNotification({
title: "删除结果通知",
message: "删除数据[" + row.ID + "]成功!",
type: 'success',
duration: 4000,
"show-close": true,
})
rows.value.splice(index, 1)
}, (err) => {
console.log("delet error:", err)
})
}).catch(() => {
})
}
function addItem(fieldDescInfo) {
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 d = {id: item.value.id, num: Number(item.value.num)};
for (let i = 0; i < fieldDescInfo.choices.length; i++) {
const field = fieldDescInfo.choices[i]
if (field.value === item.value.id) {
d.item_type = field.type
break
}
}
console.log("add item:", d)
if (typeof dialogAddForm.value.Attach === typeof "") {
dialogAddForm.value.Attach = [];
}
dialogAddForm.value.Attach.push(d);
}
function deleteItem(row) {
//
let number = form.value.Attach.findIndex(item => item === row);
dialogAddForm.value.Attach.splice(number, 1);
}
const handlePermitCheckChange = (node, isChecked, hasChildChecked) => {
if (isChecked) {
if (node.key == "project") {
//
node.children.forEach(resource => {
resource.children.forEach(method => {
selectedPermitList.value.push(method.permissionStr)
})
})
} else if (node.key == "resource") {
//
node.children.forEach(method => {
selectedPermitList.value.push(method.permissionStr)
})
} else {
selectedPermitList.value.push(node.permissionStr)
}
} else {
if (node.key == "project") {
//
node.children.forEach(resource => {
resource.children.forEach(method => {
selectedPermitList.value = selectedPermitList.value.filter(e => e !== method.permissionStr)
})
})
} else if (node.key == "resource") {
//
node.children.forEach(method => {
selectedPermitList.value = selectedPermitList.value.filter(e => e !== method.permissionStr)
})
} else {
selectedPermitList.value = selectedPermitList.value.filter(e => e !== node.permissionStr)
}
}
console.log("权限被点击了:", node, isChecked, hasChildChecked);
console.log("权限点击后:", selectedPermitList.value)
}
const handleCloseDialog = () => {
console.log("关闭添加/编辑弹窗")
dialogAddVisible.value = false
dialogEditVisible.value = false
dialogAddForm.value = {
Attach: [],
Permissions: [],
}
dialogEditForm.value = {}
selectedPermitList.value = []
}
</script>
<template>
<template v-if="!hasListPermit">
<component :is="empty"></component>
</template>
<template v-else>
<el-container v-if="listDataOK">
<el-header>
<el-button @click="dialogAddVisible = true" size="large" type="primary"
v-if="(resource_raw_node.meta.methods.post === true)">
添加
</el-button>
</el-header>
<el-main>
<el-table :data="rows" style="width: 100%" table-layout="auto" stripe>
<template v-for="fieldDescInfo in fieldsDescInfo">
<el-table-column prop="jsonValue" :label="fieldDescInfo.name"
v-if="(fieldDescInfo.type === 'items')"></el-table-column>
<el-table-column prop="jsonValue" :label="fieldDescInfo.name"
v-else-if="(fieldDescInfo.key === 'UserPass')"></el-table-column>
<el-table-column :prop="fieldDescInfo.key" :label="fieldDescInfo.name"
v-else-if="(fieldDescInfo.key === 'Permissions')" show-overflow-tooltip></el-table-column>
<el-table-column :prop="fieldDescInfo.key" :label="fieldDescInfo.name"
v-else></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)"
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)"
v-if="(resource_raw_node.meta.methods.delete === true)">
<el-icon style="vertical-align: middle">
<Delete/>
</el-icon>
<span>删除</span>
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 表格数据分页 -->
<div class="demo-pagination-block">
<div class="demonstration"></div>
<el-pagination
v-model:current-page="current_page"
v-model:page-size="page_size"
:page-sizes="[20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="400"
/>
</div>
<el-dialog v-model="dialogAddVisible" :mask="true" title="添加" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogAddFormRef" :model="dialogAddForm" :rules="rules" label-position="right"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如何是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" 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 placeholder="--选择道具后填数量点击添加--" v-model="item.id" style="width: 150px"
filterable>
<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>
<el-form-item label="数量" prop="num">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
</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="Attach">
<el-table :data="dialogAddForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</template>
<template v-else-if="(fieldDescInfo.readonly !== true)">
<!-- 有可选项的字段走下拉框或者多选框 -->
<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 ? '--多选--' : '--单选--')"
v-model="dialogAddForm[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="dialogAddForm[fieldDescInfo.key]" type="datetime"
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-if="(fieldDescInfo.key === 'Permissions')">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-tree ref="treeRef" :data="permitTree" show-checkbox node-key="id"
:props="permitTreeDefaultProps" @check-change="handlePermitCheckChange">
</el-tree>
</el-form-item>
</template>
<!-- 否则就是普通字段 -->
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogAddForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text" show-password
v-if="(fieldDescInfo.key === 'UserPass')"></el-input>
<el-input v-model="dialogAddForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text"
v-else></el-input>
</el-form-item>
</template>
</template>
</template>
<el-form-item>
<el-button @click="submitAdd(dialogAddFormRef)" size="large" type="primary">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
<el-dialog v-model="dialogEditVisible" :mask="true" title="编辑" :modal="true" :before-close="handleCloseDialog"
destroy-on-close>
<el-form ref="dialogEditFormRef" :model="dialogEditForm" :rules="rules" class="operation_form"
label-width="130px">
<template v-for="fieldDescInfo in fieldsDescInfo">
<!--如果是items类型就是物品下拉框+道具组合-->
<template v-if="(fieldDescInfo.type === 'items')">
<el-form :inline="true" :model="item" 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.id" style="width: 150px"
filterable>
<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>
<el-form-item label="数量" prop="number">
<el-input type="number" v-model="item.num" placeholder="请输入数量" style="width: 150px"/>
</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-table :data="dialogEditForm.Attach" border>
<el-table-column label="道具id" prop="id"/>
<el-table-column label="数量" prop="num"/>
<el-table-column label="操作">
<template #default="scope">
<el-button type="danger" size="small" @click="deleteItem(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form-item>
</template>
<template v-else-if="(fieldDescInfo.readonly !== true)">
<template v-if="(fieldDescInfo.uneditable !== true)">
<!-- 有可选项的字段走下拉框或者多选框 -->
<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 ? '--多选--' : '--单选--')"
v-model="dialogEditForm[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="dialogEditForm[fieldDescInfo.key]" type="datetime"
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-if="(fieldDescInfo.key === 'Permissions')">
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-tree ref="treeRef" :data="permitTree" show-checkbox node-key="id"
:default-checked-keys="selectedPermitList"
:props="permitTreeDefaultProps" @check-change="handlePermitCheckChange">
</el-tree>
</el-form-item>
</template>
<!-- 否则就是普通字段 -->
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogEditForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text" show-password
v-if="(fieldDescInfo.key === 'UserPass')"></el-input>
<el-input v-model="dialogEditForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text"
v-else></el-input>
</el-form-item>
</template>
</template>
<template v-else>
<el-form-item :label="fieldDescInfo.name" :prop="fieldDescInfo.key">
<el-input v-model="dialogEditForm[fieldDescInfo.key]"
:placeholder="fieldDescInfo.help_text" 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-item>
<el-button @click="submitEdit(dialogEditFormRef)" size="large" type="primary">提交</el-button>
</el-form-item>
</el-form>
</el-dialog>
</el-main>
</el-container>
</template>
</template>
<style scoped lang="scss">
.demo-pagination-block {
margin-top: 10px;
.el-pagination {
right: 40px;
position: absolute;
}
}
</style>

View File

@ -1,12 +1,21 @@
import router, {projectOpTreeRoutes, setProjectOperationRoutes} from './router'
import {generateRoutes} from "@/api/game_api.js";
import LocalCache, {cachedProject, cachedProjectResource} from '@/stores/project.js'
import router, {isGetUserInfo, projectOpTreeRoutes, setProjectOperationRoutes} from './router'
import {cachedProject, cachedProjectResource} from '@/stores/project.js'
import {getUserInfo} from "@/api/sys.js";
import LocalCache from "@/stores/localCache.js";
router.beforeEach((to, from, next) => {
if (to.path === '/login') {
// 推首页
next({path: '/'})
next()
} else {
const token = LocalCache.getCache("token");
if (!token) {
// 没有token
console.log("重定向到login")
next('/login?redirect=/home') // 没有token都重定向到登录页面
return
}
// 有token就去拉最新的用户数据动态生成菜单
if (to.path.startsWith('/project')) {
const pathSegments = to.path.split("/").filter(segment => segment !== "");
if (pathSegments.length === 3) {
@ -22,18 +31,23 @@ router.beforeEach((to, from, next) => {
}
}
if (projectOpTreeRoutes.value.length === 0) {
generateRoutes().then((res) => {
if (isGetUserInfo.value === false) {
getUserInfo().then((res) => {
const projectList = ref([])
// res.data.user
// res.data.token
projectList.value = res.data.projects
LocalCache.setCache('user', res.data.user_info)
setProjectOperationRoutes(projectList.value)
// console.log("all routes:", router.getRoutes())
next({...to, replace: true}); // 在已有页面里查找
}, (err) => {
console.log("跳转路径:", to.path, " 报错:", err)
LocalCache.deleteCache("token")
LocalCache.deleteCache("user")
})
} else {
console.log("op tree routes length valid:", projectOpTreeRoutes.value.length)
// console.log("op tree routes length valid:", projectOpTreeRoutes.value.length)
next()
}
}

View File

@ -1,6 +1,8 @@
import {createRouter, createWebHistory} from 'vue-router'
import Home from '../views/Home.vue'
import LocalCache from "@/stores/localCache.js";
export const isGetUserInfo = ref(false) // 是否获取过后端路由接口,用于第一次进页面或者刷新页面拉取路由恢复菜单
export const projectOpTreeRoutes = ref([]) // 项目操作的树形路由,用于菜单树生成
export const projectOpFlatRoutes = ref([]) // 项目操作的展平路由用于直接挂钩到父组件即home页面否则点击子菜单无法在父页面内容区显示
@ -13,13 +15,41 @@ export const constProjectResourceRoute = {
resource_url: "/project",
methods: {
get: true,
post: true,
put: true,
delete: true,
}
},
component: () => import('@/views/project/project.vue'),
}
export const constUserChildrenRoutes = [
{
path: "/usermanager",
name: "usermanager",
meta: {
name: "用户管理",
icon: "User",
},
component: () => import('@/views/user/user.vue')
},
{
path: "/character",
name: "character",
meta: {
name: "角色管理",
icon: "Avatar",
},
component: () => import('@/views/user/character.vue'),
}
]
export const constHomeChildrenRoutes = [
{
path: '/welcome',
name: 'welcome',
component: () => import('@/views/welcome.vue')
},
{
path: '/user',
name: 'user',
@ -27,7 +57,7 @@ export const constHomeChildrenRoutes = [
name: "user",
icon: "User",
},
component: () => import('@/views/user/user.vue')
children: constUserChildrenRoutes,
},
constProjectResourceRoute,
]
@ -42,6 +72,12 @@ const homeRoute = {
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/login",
name: "login",
component: () => import("@/views/Login.vue"),
hidden: true
},
homeRoute,
],
})
@ -51,11 +87,13 @@ export default router
export function setProjectOperationRoutes(projectList) {
// console.log("resourceList:", projectList)
isGetUserInfo.value = true
projectOpTreeRoutes.value = []
projectOpFlatRoutes.value = []
for (let i = 0; i < projectList.length; i++) {
const project = projectList[i]
let projectHasAnyResourcePermission = false // 判断项目是否至少有一个资源权限,否则就不显示这个项目菜单
const projectRoute = {
path: '/project/' + project.project_id,
name: project.project_id,
@ -85,6 +123,9 @@ export function setProjectOperationRoutes(projectList) {
props: true
}
resource.show_methods.forEach((method) => {
if (method == "get") {
projectHasAnyResourcePermission = true
}
resourceRoute.meta.methods[method] = true
})
@ -95,10 +136,13 @@ export function setProjectOperationRoutes(projectList) {
// console.log("add resource route:", resourceRoute)
})
projectOpTreeRoutes.value.push(projectRoute)
if (projectHasAnyResourcePermission) {
projectOpTreeRoutes.value.push(projectRoute)
}
homeRoute.children = constHomeChildrenRoutes.concat(projectOpFlatRoutes.value)
router.addRoute(homeRoute)
}
console.log("after set all routes:", router.getRoutes())
LocalCache.setCache("projectsRoute", projectList)
// console.log("after set all routes:", router.getRoutes())
}

View File

@ -0,0 +1,43 @@
import localCache from "@/stores/localCache";
class ExpireCache {
// 添加
setCache(key, value, expire) {
let obj = {
data: value,
time: Date.now(), // 毫秒时间戳
expire: expire * 1000, // 传进来秒
}
const realKey = key + ".expire"
localCache.setCache(realKey, obj)
// window.localStorage.setItem(key, JSON.stringify(obj))
}
// 查找
getCache(key) {
const realKey = key + ".expire"
let val = localCache.getCache(realKey)
if (!val) {
return val
} else {
if (Date.now() - val.time > val.expire) {
localCache.deleteCache(realKey)
return null
}
return val.data
}
}
// 删除
deleteCache(key) {
const realKey = key + ".expire"
localCache.deleteCache(realKey)
}
// 清理
clearCache() {
// localCache.clearCache()
}
}
export default new ExpireCache()

View File

@ -0,0 +1,27 @@
class LocalCache {
// 添加
setCache(key, value) {
window.localStorage.setItem(key, JSON.stringify(value))
}
// 查找
getCache(key) {
// obj=>sting=>obj
const value = window.localStorage.getItem(key)
if (value) {
return JSON.parse(value)
}
}
// 删除
deleteCache(key) {
window.localStorage.removeItem(key)
}
// 清理
clearCache() {
// window.localStorage.clear()
}
}
export default new LocalCache()

View File

@ -1,33 +1,9 @@
import {ref} from 'vue'
import {defineStore} from 'pinia'
class LocalCache {
// 添加
setCache(key, value) {
window.localStorage.setItem(key, JSON.stringify(value))
}
// 查找
getCache(key) {
// obj=>sting=>obj
const value = window.localStorage.getItem(key)
if (value) {
return JSON.parse(value)
}
}
// 删除
deleteCache(key) {
window.localStorage.removeItem(key)
}
// 清理
clearCache() {
// window.localStorage.clear()
}
}
export default new LocalCache()
/*
项目pinia缓存刷新页面就清空了适合一些页面跳转传参逻辑
*/
export const cachedProject = defineStore('clickedProject', () => {
const project = ref({})

View File

@ -0,0 +1,73 @@
export function getPermissionTreeByProjects(projectsRoute) {
// 权限字段
let projectNodes = []
let id = 0;
projectsRoute.forEach(pro => {
id++
let proNode = {
id: id,
label: pro.project_name,
key: "project",
children: []
}
pro.resource_total_list.forEach(resource => {
id++
let resourceNode = {
id: id,
label: resource.desc,
key: "resource",
children: []
}
// console.log("资源方法:", resource.desc, resource.show_methods)
for (let i = 0; i < resource.show_methods.length; i++) {
id++
const method = resource.show_methods[i]
const permissionStr = resource.methods_permission[i]
let methodDesc = ""
if (method === "get") {
methodDesc = "查看"
} else if (method === "post") {
methodDesc = "添加"
} else if (method === "put") {
methodDesc = "编辑"
} else if (method === "delete") {
methodDesc = "删除"
}
const methodNode = {
id: permissionStr,
label: methodDesc,
key: pro.project_id + "_" + resource.resource + "_" + method,
permissionStr: permissionStr,
}
// console.log("资源方法:", resource.desc, methodNodea)
resourceNode.children.push(methodNode)
}
proNode.children.push(resourceNode)
})
projectNodes.push(proNode)
})
return projectNodes
}
export function getSelectedPermissions(projectNodes, hasPermissions) {
let defaultCheckedKeys = []
projectNodes.forEach(proNode => {
proNode.children.forEach(resourceNode => {
resourceNode.children.forEach(methodNode => {
for (let i = 0; i < hasPermissions.length; i++) {
if (hasPermissions[i] === methodNode.permissionStr) {
defaultCheckedKeys.push(methodNode.id)
break
}
}
})
})
})
return defaultCheckedKeys
}

View File

@ -2,16 +2,19 @@ import axios from 'axios'
import {ElMessageBox} from "element-plus";
// import LocalCache from "@/stores/localCache";
import {useRouter} from "vue-router";
import LocalCache from "@/stores/localCache.js";
// import ExpireCache from "@/stores/expireCache";
// 创建axios实例
const service=axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000,
headers: {
"Content-type": "application/json;charset=utf-8",
"Cache-Control": 'no-cache',
}
const service = axios.create({
baseURL: import.meta.env.VITE_APP_BASE_API,
timeout: 10000,
headers: {
"Content-type": "application/json;charset=utf-8",
"Cache-Control": 'no-cache',
"UserId": 0,
"Token": "",
}
})
// axios.post('192.168.1.60:8888/login', {username: "likun", password: "likun1"}).then((res)=>{
@ -21,41 +24,57 @@ const service=axios.create({
// console.log(err)
// })
const reqInterceptor = (config)=>{
config.headers=config.headers || {}
// const token = ExpireCache.getCache('token')
// if(token){
// config.headers["x-token"]=token || ""
// } else {
// console.log("not found local storage token")
// }
return config
const reqInterceptor = (config) => {
let user = LocalCache.getCache("user");
let token = LocalCache.getCache("token");
let userId = user ? parseInt(user.user_id, 10) : 0;
token = token ? token : "";
config.headers = config.headers || {}
config.headers.UserId = userId;
config.headers.Token = token;
// const token = ExpireCache.getCache('token')
// if(token){
// config.headers["x-token"]=token || ""
// } else {
// console.log("not found local storage token")
// }
return config
}
const resInterceptor = (res)=>{
console.log("res:", res.data)
const code=res.data.code
if(code!=200) {
console.log("interceptor err code", res)
ElMessageBox.alert("请求服务器成功,但是逻辑错误:" + res.data.message, "服务器错误码" + code, {
type: "warning",
confirmButtonText: '知道了',
const resInterceptor = (res) => {
console.log("res:", res.data)
const code = res.data.code
if (code != 200) {
if (code === 5) {
// token过期
console.log("token无效重新登录")
location.href = "/login"
return Promise.reject()
}
console.log("interceptor err code", res)
ElMessageBox.alert("请求服务器成功,但是逻辑错误:" + res.data.msg, "服务器错误码[" + code + "]", {
type: "warning",
confirmButtonText: '知道了',
})
return Promise.reject(res.data)
}
return res.data
}
const resErrorInterceptor = (err) => {
console.log(err)
const code = err.response && err.response.status || -1
const message = err.response && err.response.data.message || err
ElMessageBox.alert(message, "请求服务器返回http错误码-" + code, {
type: "error",
confirmButtonText: '知道了',
})
return Promise.reject(res.data)
}
return res.data
}
const resErrorInterceptor = (err)=>{
console.log(err)
const code = err.response && err.response.status || -1
const message = err.response && err.response.data.message || err
ElMessageBox.alert(message, "请求服务器返回http错误码-" + code, {
type: "error",
confirmButtonText: '知道了',
})
return Promise.reject(err)
return Promise.reject(err)
}
// 请求拦截

View File

@ -2,22 +2,59 @@
import {useRoute, useRouter} from 'vue-router'
import {computed} from 'vue'
import avatarUrl from '@/assets/icon/header.png'
import {projectOpTreeRoutes} from '@/router/index.js'
import LocalCache from "@/stores/project.js";
import {constUserChildrenRoutes, projectOpTreeRoutes} from '@/router/index.js'
import LocalCache from "@/stores/localCache.js";
import welcome from '@/views/welcome.vue'
const router = useRouter()
const route = useRoute()
const userInfo = LocalCache.getCache("user")
const nickName = userInfo.nick_name
//
const activeMenu = computed(() => route.path)
const hasClickedMenu = ref(false)
router.push("/welcome")
const handleEnterIndex = () => {
router.push("/welcome")
}
//
const handleMenuSelect = (clickResource) => {
console.log("点击资源:", clickResource)
LocalCache.setCache("resource", clickResource)
router.push({path: clickResource.path})
hasClickedMenu.value = true
}
function handleCommand(command) {
switch (command) {
case "logout":
logout();
break;
default:
break;
}
}
function logout() {
ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
LocalCache.deleteCache("user")
LocalCache.deleteCache("token")
router.push('/login')
}).catch(() => {
});
}
</script>
<template>
@ -27,23 +64,35 @@ const handleMenuSelect = (clickResource) => {
<el-aside class="el-aside-demo">
<div class="sidebar-content">
<!-- <el-avatar shape="square" :size="100" :src="avatarUrl"></el-avatar>-->
<div class="sidebar-logo">
<div class="sidebar-logo" @click="handleEnterIndex">
<img src="@/assets/logo.svg" class="logo" alt="Logo">
<span class="system-name">游戏后台管理系统</span>
</div>
<el-menu :default-active="activeMenu" class="el-menu-vertical-demo" :collapse="false">
<!-- 静态菜单 -->
<el-menu-item index="/user" @click="$router.push('/user')">
<span>用户管理</span>
</el-menu-item>
<el-menu-item index="/project" @click="$router.push('/project')">
<span>项目管理</span>
</el-menu-item>
<div style="border-bottom: 1px whitesmoke solid">
<!-- 静态菜单 -->
<el-sub-menu index="/user">
<template #title>
<span>用户管理</span>
</template>
<el-menu-item v-for="user in constUserChildrenRoutes" :index="user.path"
@click="$router.push(user.path)">
<el-icon>
<component class="el-icon" :is="user.meta.icon"></component>
</el-icon>
<span>{{ user.meta.name }}</span>
</el-menu-item>
</el-sub-menu>
<el-menu-item index="/project" @click="$router.push('/project')">
<span>项目管理</span>
</el-menu-item>
</div>
<!-- 分割条 -->
<el-divider></el-divider>
<!-- <el-divider></el-divider>-->
<!-- 动态菜单每个项目操作 -->
<template v-for="project in projectOpTreeRoutes">
@ -69,11 +118,11 @@ const handleMenuSelect = (clickResource) => {
<el-header class="el-header-demo">
<div class="header-content">
<div class="avatar-container">
<el-dropdown class="right-menu-item hover-effect" trigger="click">
<el-dropdown class="right-menu-item hover-effect" trigger="click" @command="handleCommand">
<!-- 头像 -->
<div class="avatar-wrapper">
<img :src="avatarUrl" class="user-avatar"/>
<span style="font-size: 20px">{{ nickName }}</span>
<el-icon color="black" size="20">
<ArrowDownBold/>
</el-icon>
@ -83,8 +132,8 @@ const handleMenuSelect = (clickResource) => {
<!-- 头像操作下拉菜单 -->
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>退出登录</el-dropdown-item>
<!-- <el-dropdown-item>个人中心</el-dropdown-item>-->
<el-dropdown-item command="logout">退出登录</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@ -158,6 +207,7 @@ h1 {
background: transparent; /* 关键:透明背景 */
position: relative;
z-index: 1;
cursor: pointer;
.logo {
width: 50px;
@ -187,6 +237,11 @@ h1 {
background-color: cadetblue;
}
:deep(.el-menu-vertical-demo .el-sub-menu) {
color: #fff;
background-color: cadetblue;
}
:deep(.el-menu-vertical-demo .el-sub-menu__title) {
color: #fff;
}
@ -220,13 +275,8 @@ h1 {
.avatar-wrapper {
margin-top: 5px;
position: relative;
.user-avatar {
cursor: pointer;
width: 40px;
height: 40px;
border-radius: 10px;
}
align-items: center;
cursor: pointer;
i {
cursor: pointer;

262
ui/src/views/Login.vue Normal file
View File

@ -0,0 +1,262 @@
<template>
<div class="login-box">
<div :class="{ active: currentModel, container: true,animate__animated :true, animate__flipInX :true}">
<div class="form-container sign-in-container">
<el-form
ref="ruleFormRef"
:model="loginForm"
status-icon
:rules="loginRules"
class="form"
>
<el-form-item class="form-item" prop="username">
<el-input v-model="loginForm.user" placeholder="用户名" autocomplete="off"
@keyup.enter="submitForm(loginForm)"/>
</el-form-item>
<el-form-item class="form-item" prop="password">
<el-input
v-model="loginForm.password"
placeholder="密码"
type="password"
autocomplete="off"
@keyup.enter="submitForm(loginForm)"
/>
</el-form-item>
<!-- <el-form-item>-->
<el-button class="theme-button" type="primary" @click="submitForm(loginForm)" @keydown.enter="keyDown()">
</el-button>
<p class="forget"
@click="
currentModel = !currentModel;
isForget = true;
">
--- Forget Passwoed----
</p>
<!-- </el-form-item>-->
</el-form>
</div>
<!--覆盖容器-->
<div class="overlay_container">
<div class="overlay">
<div class="overlay_panel overlay_right_container">
<h2 class="container-title">hello friend!</h2>
<p>输入您的个人信息以便使用后台管理系统</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import {defineComponent, reactive, toRefs, ref} from "vue";
import LocalCache from "@/stores/localCache";
import ExpireCache from "@/stores/expireCache";
import {login} from "@/api/sys.js";
import router from "@/router/index.js";
const redirect = ref(undefined);
const {proxy} = getCurrentInstance();
const loginForm = ref({
user: "",
password: "",
})
const loginRules = {
user: [{required: true, trigger: "blur", message: "请输入您的账号"}],
password: [{required: true, trigger: "blur", message: "请输入您的密码"}],
// code: [{ required: true, trigger: "change", message: "" }]
};
const submitForm = (formEl) => {
if (!formEl) return
//
proxy.$refs.ruleFormRef.validate((valid) => {
if (valid) {
login(loginForm.value).then((res) => {
// token
// console.log('', res.payload.username)
// console.log('token', res.payload.token)
LocalCache.setCache('token', res.data.token_info.token)
LocalCache.setCache('user', res.data.user_info)
ExpireCache.setCache("token", res.data.token_info.token, res.data.token_info.expire_at)
console.log("登录成功,返回数据:", res.data, ",准备重定向到页面:", redirect.value)
router.push({path: redirect.value || "/"})
}, (res) => {
console.log("login response error:")
console.log(res)
})
} else {
console.log('error submit!')
return false
}
})
}
const resetForm = () => {
loginForm.value.user = ""
loginForm.value.password = ""
}
const keyDown = (e) => {
if (e.keyCode === 13 || e.keyCode === 100) {
submitForm(loginForm.value)
}
}
</script>
<style lang="scss" scoped>
.login-box {
width: 100%;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f6f5f7;
background-size: cover;
background-image: url("@/assets/img/login-background.svg");
}
.container {
position: relative;
width: 768px;
height: 480px;
background-color: white;
box-shadow: 0 14px 28px rgba(0, 0, 0, 0.25), 0 10px 10px rgba(0, 0, 0, 0.2);
border-radius: 10px;
overflow: hidden;
}
.form-container {
position: absolute;
top: 0;
width: 50%;
height: 100%;
/*border: solid;*/
background-color: white;
transition: all 0.6s ease-in-out;
}
.form {
display: flex;
text-align: -webkit-center;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
padding: 0 50px;
/*border: solid;*/
}
.form-item {
width: 100%;
/*border: solid;*/
:deep(.el-input-group__append) {
:deep(.el-button) {
width: 60px;
}
}
;
}
:deep(.el-input__wrapper) {
border-radius: 30px;
}
:deep(.el-input-group__append) {
border-radius: 20px;
margin-left: 10px;
background: white;
color: #fafafa;
}
.forget {
font-size: 12px;
color: rgba(50, 50, 50, 0.6);
position: relative;
top: 50px;
}
button:active {
transform: scale(0.95);
}
.container.active .sign-up-container {
transform: translateX(100%);
z-index: 5;
}
.container.active .sign-in-container {
transform: translateX(100%);
}
.container.active .overlay_container {
transform: translateX(-100%);
}
.container.active .overlay {
transform: translateX(50%);
}
.theme-button {
background: rgba(64, 158, 255, 0.7);
padding: 10px 50px;
border: 1px solid transparent;
border-radius: 20px;
text-transform: uppercase;
color: white;
margin-top: 10px;
outline: none;
transition: transform 80;
}
.overlay_container {
position: absolute;
top: 0;
width: 50%;
height: 100%;
z-index: 100;
right: 0;
overflow: hidden;
transition: all 0.6s ease-in-out;
}
.overlay {
position: absolute;
width: 200%;
height: 100%;
left: -100%;
background-color: #42b983;
}
.overlay_panel {
position: absolute;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 50%;
height: 100%;
color: white;
padding: 0 0px;
text-align: center;
}
.overlay_panel button {
background-color: transparent;
border: 1px solid white;
}
.overlay_panel p {
font-size: 12px;
margin: 10px 0 15px 0;
}
.overlay_right_container {
right: 0;
}
</style>

View File

@ -1,7 +1,7 @@
<script setup>
import tableView from '@/components/restful/table.vue'
import LocalCache from "@/stores/project.js";
import LocalCache from "@/stores/localCache.js";
import {useRouter} from 'vue-router'
import {constProjectResourceRoute} from "@/router/index.js";
// import {Aim, ArrowRightBold} from '@element-plus/icons-vue'
@ -10,7 +10,12 @@ LocalCache.setCache("project", {})
const router = useRouter()
LocalCache.setCache("resource", constProjectResourceRoute)
let resourceCache = constProjectResourceRoute
const userInfo = LocalCache.getCache("user")
if (userInfo.character !== "admin") {
resourceCache.meta.methods = {} //
}
LocalCache.setCache("resource", resourceCache)
</script>

View File

@ -0,0 +1,34 @@
<script setup>
import tableView from "@/components/restful/tableUser.vue";
import LocalCache from "@/stores/localCache.js";
let resourceCache = {
meta: {
desc: 'character',
resource: 'character',
resource_url: '/resource/character',
methods: {
get: true,
post: true,
put: true,
delete: true,
},
},
}
const userInfo = LocalCache.getCache("user")
if (userInfo.character !== "admin") {
resourceCache.meta.methods = {} //
}
LocalCache.setCache("resource", resourceCache)
</script>
<template>
<div>
<component :is="tableView"></component>
</div>
</template>
<style scoped>
</style>

View File

@ -1,9 +1,32 @@
<script setup>
import tableView from "@/components/restful/tableUser.vue";
import LocalCache from "@/stores/localCache.js";
let resourceCache = {
meta: {
desc: 'user',
resource: 'user',
resource_url: '/resource/user',
methods: {
get: true,
post: true,
put: true,
delete: true,
},
},
}
const userInfo = LocalCache.getCache("user")
if (userInfo.character !== "admin") {
resourceCache.meta.methods = {} //
}
LocalCache.setCache("resource", resourceCache)
</script>
<template>
用户管理
<div>
<component :is="tableView"></component>
</div>
</template>
<style scoped>

18
ui/src/views/welcome.vue Normal file
View File

@ -0,0 +1,18 @@
<script setup>
import LocalCache from "@/stores/localCache.js";
const userInfo = LocalCache.getCache("user")
</script>
<template>
<span style="font-size:40px">亲爱的<span style="color: darkslategrey;font-size: 50px"> {{
userInfo.nick_name
}} </span>欢迎使用本公司后台管理系统</span>
<el-divider></el-divider>
<span style="font-size:40px">硬盘有价数据无价操作不规范亲人两行泪</span>
</template>
<style scoped>
</style>