add user role auth page

master
veypi 3 years ago
parent bc3f5e0b0c
commit bcbfc0380a

@ -6,13 +6,29 @@ import (
"OneAuth/api/token" "OneAuth/api/token"
"OneAuth/api/user" "OneAuth/api/user"
"OneAuth/api/wx" "OneAuth/api/wx"
"OneAuth/cfg"
"OneAuth/libs/base" "OneAuth/libs/base"
"OneAuth/libs/oerr"
"crypto/md5"
"encoding/hex"
"fmt"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/core" "github.com/veypi/OneBD/core"
"github.com/veypi/OneBD/rfc"
"github.com/veypi/utils"
"github.com/veypi/utils/log"
"io"
"os"
"path"
"path/filepath"
"regexp"
"strconv"
"time"
) )
func Router(r OneBD.Router) { func Router(r OneBD.Router) {
r.SetNotFoundFunc(func(m core.Meta) { r.SetNotFoundFunc(func(m core.Meta) {
log.Info().Msgf("%s", m.Request().RequestURI)
base.JSONResponse(m, nil, nil) base.JSONResponse(m, nil, nil)
}) })
r.SetInternalErrorFunc(func(m core.Meta) { r.SetInternalErrorFunc(func(m core.Meta) {
@ -20,9 +36,98 @@ func Router(r OneBD.Router) {
}) })
user.Router(r.SubRouter("/user")) user.Router(r.SubRouter("/user"))
wx.Router(r.SubRouter("wx")) wx.Router(r.SubRouter("wx"))
app.Router(r.SubRouter("app")) appRouter := r.SubRouter("app")
token.Router(r.SubRouter("token")) app.Router(appRouter)
role.Router(r) role.Router(appRouter.SubRouter("/:uuid/"))
token.Router(appRouter.SubRouter("/:uuid/token"))
r.Set("upload", handleUpload, rfc.MethodPost)
//message.Router(r.SubRouter("/message")) //message.Router(r.SubRouter("/message"))
} }
func handleUpload(m OneBD.Meta) {
r := m.Request()
length, err := strconv.Atoi(r.Header.Get("Content-Length"))
if err != nil {
base.JSONResponse(m, nil, err)
return
}
bufLen := 32 << 10
buf := make([]byte, bufLen)
// 缓存上一级读取数据, 使用copy 复制数据 最多缓存2倍buf, 为倒数第1,2桢
bufCache := make([]byte, 2*bufLen)
bufCacheLen := 0
headerReg := regexp.MustCompile(`------WebKitFormBoundary.*\r\n.*?filename="([^"]+)".*\r\n(.*)\r\n\r\n(?s:(.*))`)
footReg := regexp.MustCompile(`(?s:(.*))\r\n------WebKitFormBoundary.*?--\r\n`)
fileName := ""
fileDir, err := filepath.Abs(cfg.CFG.MediaDir)
if err != nil {
base.JSONResponse(m, nil, err)
return
}
hash := md5.New()
var written int64 = 0
firstRead := true
nr := 0
var er error
var out *os.File
var filePath string
for {
nr, er = r.Body.Read(buf)
if er != nil && er != io.EOF {
base.JSONResponse(m, nil, err)
return
}
if firstRead {
firstRead = false
res := headerReg.FindAllSubmatch(buf[0:nr], -1)
if len(res) == 0 || len(res[0]) == 0 {
log.Warn().Msgf("reg failed for header form: %s: %d %d", buf, bufLen, length)
base.JSONResponse(m, nil, oerr.ApiArgsError)
return
}
fileName = string(res[0][1])
copy(bufCache, res[0][3])
bufCacheLen = len(res[0][3])
nr = 0
filePath = fmt.Sprintf("upload/%s_%s_%s", time.Now().Format("2006-01-02-15-04-05"), utils.RandSeq(5), fileName)
out, err = os.OpenFile(path.Join(fileDir, filePath),
os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
base.JSONResponse(m, nil, err)
return
}
defer out.Close()
}
if er == io.EOF {
res := footReg.FindAllSubmatch(append(bufCache[0:bufCacheLen], buf[0:nr]...), -1)
if len(res) == 0 || len(res[0]) == 0 {
log.Warn().Msgf("reg failed for foot form: %s", buf[0:nr])
base.JSONResponse(m, nil, oerr.ApiArgsError)
return
}
copy(bufCache, res[0][1])
bufCacheLen = len(res[0][1])
}
if bufCacheLen > 0 {
if nw, ew := out.Write(bufCache[0:bufCacheLen]); ew != nil {
base.JSONResponse(m, nil, ew)
return
} else if nw > 0 {
written += int64(nw)
}
if _, ew := hash.Write(bufCache[0:bufCacheLen]); ew != nil {
base.JSONResponse(m, nil, ew)
return
}
}
copy(bufCache, buf[0:nr])
bufCacheLen = nr
if er == io.EOF {
break
}
}
_ = hex.EncodeToString(hash.Sum(nil))
base.JSONResponse(m, "/media/"+filePath, nil)
return
}

@ -11,11 +11,6 @@ import (
"github.com/veypi/utils" "github.com/veypi/utils"
) )
func Router(r OneBD.Router) {
r.Set("/", appHandlerP, rfc.MethodPost, rfc.MethodGet)
r.Set("/:id", appHandlerP, rfc.MethodGet)
}
var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler { var appHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler {
h := &appHandler{} h := &appHandler{}
h.Ignore(rfc.MethodGet, rfc.MethodPost) h.Ignore(rfc.MethodGet, rfc.MethodPost)
@ -28,10 +23,10 @@ type appHandler struct {
} }
func (h *appHandler) Get() (interface{}, error) { func (h *appHandler) Get() (interface{}, error) {
id := h.Meta().Params("id") uuid := h.Meta().Params("uuid")
h.query = &models.App{} h.query = &models.App{}
isSelf := h.Meta().Query("is_self") option := h.Meta().Query("option")
if isSelf != "" { if option == "oa" {
// 无权限可以获取本系统基本信息 // 无权限可以获取本系统基本信息
h.query.UUID = cfg.CFG.APPUUID h.query.UUID = cfg.CFG.APPUUID
err := cfg.DB().Where(h.query).First(h.query).Error err := cfg.DB().Where(h.query).First(h.query).Error
@ -41,11 +36,11 @@ func (h *appHandler) Get() (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !h.GetAuth(auth.APP, id).CanRead() { if !h.GetAuth(auth.APP, uuid).CanRead() {
return nil, oerr.NoAuth return nil, oerr.NoAuth
} }
if id != "" { if uuid != "" {
h.query.UUID = id h.query.UUID = uuid
err := cfg.DB().Where(h.query).First(h.query).Error err := cfg.DB().Where(h.query).First(h.query).Error
return h.query, err return h.query, err
} }
@ -98,3 +93,33 @@ func (h *appHandler) Post() (interface{}, error) {
} }
return a, nil return a, nil
} }
func (h *appHandler) Patch() (interface{}, error) {
uid := h.Meta().Params("uuid")
opts := struct {
Icon string `json:"icon"`
Name string `json:"name"`
}{}
if err := h.Meta().ReadJson(&opts); err != nil {
return nil, err
}
target := models.App{
UUID: uid,
}
if err := cfg.DB().Where(&target).First(&target).Error; err != nil {
return nil, err
}
if !h.Payload.GetAuth(auth.APP, target.UUID).CanUpdate() {
return nil, oerr.NoAuth
}
if opts.Name != "" {
target.Name = opts.Name
}
if opts.Icon != "" {
target.Icon = opts.Icon
}
if err := cfg.DB().Updates(&target).Error; err != nil {
return nil, err
}
return nil, nil
}

@ -0,0 +1,12 @@
package app
import (
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc"
)
func Router(r OneBD.Router) {
r.Set("/", appHandlerP, rfc.MethodPost, rfc.MethodGet)
r.Set("/:uuid", appHandlerP, rfc.MethodGet, rfc.MethodPatch)
r.Set("/:uuid/user/:id", auHandlerP, rfc.MethodAll)
}

@ -0,0 +1,108 @@
package app
import (
"OneAuth/cfg"
"OneAuth/libs/app"
"OneAuth/libs/auth"
"OneAuth/libs/base"
"OneAuth/libs/oerr"
"OneAuth/models"
"github.com/veypi/OneBD"
)
var auHandlerP = OneBD.NewHandlerPool(func() OneBD.Handler {
h := &appUserHandler{}
return h
})
type appUserHandler struct {
base.ApiHandler
uuid string
}
func (h *appUserHandler) Init(m OneBD.Meta) error {
h.uuid = m.Params("uuid")
if h.uuid == "-" {
h.uuid = ""
}
return h.ApiHandler.Init(m)
}
func (h *appUserHandler) Get() (interface{}, error) {
id := h.Meta().ParamsInt("id")
if h.uuid == "" && id == 0 {
return nil, oerr.ApiArgsMissing
}
if uint(id) != h.Payload.ID && !h.Payload.GetAuth(auth.User, h.uuid).CanRead() {
return nil, oerr.NoAuth
}
au := &models.AppUser{
UserID: uint(id),
AppUUID: h.uuid,
}
list := make([]*models.AppUser, 0, 10)
err := cfg.DB().Preload("User").Where(au).Find(&list).Error
return list, err
}
func (h *appUserHandler) Post() (interface{}, error) {
id := h.Meta().ParamsInt("id")
if h.uuid == "" || id <= 0 {
return nil, oerr.ApiArgsMissing
}
status := models.AUOK
target := &models.App{}
err := cfg.DB().Where("uuid = ?", h.uuid).First(target).Error
if err != nil {
return nil, err
}
if target.EnableRegister {
status = models.AUApply
}
au, err := app.AddUser(cfg.DB(), h.uuid, uint(id), target.InitRoleID, status)
return au, err
}
func (h *appUserHandler) Update() (interface{}, error) {
id := h.Meta().ParamsInt("id")
if h.uuid == "" || id <= 0 {
return nil, oerr.ApiArgsMissing
}
props := struct {
Status string `json:"status"`
}{}
err := h.Meta().ReadJson(&props)
if err != nil {
return nil, err
}
if uint(id) != h.Payload.ID && !h.Payload.GetAuth(auth.User, h.uuid).CanUpdate() {
return nil, oerr.NoAuth
}
au := &models.AppUser{
UserID: uint(id),
AppUUID: h.uuid,
}
err = cfg.DB().Where(au).Update("status", props.Status).Error
return nil, err
}
func (h *appUserHandler) Delete() (interface{}, error) {
id := h.Meta().ParamsInt("id")
if h.uuid == "" && id <= 0 {
return nil, oerr.ApiArgsMissing
}
if uint(id) != h.Payload.ID && h.Payload.GetAuth(auth.User, h.uuid).CanDelete() {
return nil, oerr.NoAuth
}
au := &models.AppUser{
AppUUID: h.uuid,
UserID: uint(id),
}
list := make([]*models.AppUser, 0, 10)
err := cfg.DB().Where(au).Delete(&list).Error
if err != nil {
return nil, err
}
err = cfg.DB().Delete(&list).Error
return nil, err
}

@ -5,9 +5,11 @@ import (
"OneAuth/libs/auth" "OneAuth/libs/auth"
"OneAuth/libs/base" "OneAuth/libs/base"
"OneAuth/libs/oerr" "OneAuth/libs/oerr"
"OneAuth/libs/token"
"OneAuth/models" "OneAuth/models"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/core" "github.com/veypi/OneBD/core"
"strconv"
) )
var authP = OneBD.NewHandlerPool(func() core.Handler { var authP = OneBD.NewHandlerPool(func() core.Handler {
@ -22,6 +24,37 @@ func (h *authHandler) Get() (interface{}, error) {
if !h.GetAuth(auth.Auth).CanRead() { if !h.GetAuth(auth.Auth).CanRead() {
return nil, oerr.NoAuth return nil, oerr.NoAuth
} }
l := make([]*models.Auth, 0, 10) aid := h.Meta().ParamsInt("id")
return &l, cfg.DB().Find(&l).Error query := &models.Auth{}
var err error
if aid > 0 {
err = cfg.DB().Where("id = ?", aid).First(query).Error
return query, err
}
id, _ := strconv.Atoi(h.Meta().Query("id"))
uuid := h.Meta().Query("uuid")
if id == 0 || uuid == "" {
return nil, oerr.ApiArgsMissing
}
target := &models.App{}
err = cfg.DB().Where("uuid = ?", uuid).First(target).Error
if err != nil {
return nil, err
}
u := &models.User{}
err = cfg.DB().Preload("Roles.Auths").Preload("Auths").Where("id = ?", id).First(u).Error
if err != nil {
return nil, err
}
l := make([]*token.SimpleAuth, 0, 10)
for _, as := range u.GetAuths() {
if as.AppUUID == uuid {
l = append(l, &token.SimpleAuth{
RID: as.RID,
RUID: as.RUID,
Level: as.Level,
})
}
}
return l, nil
} }

@ -6,7 +6,6 @@ import (
"OneAuth/libs/base" "OneAuth/libs/base"
"OneAuth/libs/oerr" "OneAuth/libs/oerr"
"OneAuth/models" "OneAuth/models"
"errors"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -15,17 +14,28 @@ var roleP = OneBD.NewHandlerPool(func() OneBD.Handler {
return &roleHandler{} return &roleHandler{}
}) })
type roleHandler struct { type baseAppHandler struct {
base.ApiHandler base.ApiHandler
uuid string
}
func (h *baseAppHandler) Init(m OneBD.Meta) error {
h.uuid = m.Params("uuid")
return h.ApiHandler.Init(m)
}
type roleHandler struct {
baseAppHandler
} }
func (h *roleHandler) Get() (interface{}, error) { func (h *roleHandler) Get() (interface{}, error) {
id := h.Meta().ParamsInt("id") id := h.Meta().ParamsInt("id")
if !h.GetAuth(auth.Role, h.Meta().Params("id")).CanRead() { if !h.GetAuth(auth.Role, h.uuid).CanRead() {
return nil, oerr.NoAuth return nil, oerr.NoAuth
} }
if id > 0 { if id > 0 {
role := &models.Role{} role := &models.Role{}
role.AppUUID = h.uuid
role.ID = uint(id) role.ID = uint(id)
err := cfg.DB().Preload("Auths").Preload("Users").First(role).Error err := cfg.DB().Preload("Auths").Preload("Users").First(role).Error
if err != nil { if err != nil {
@ -34,7 +44,7 @@ func (h *roleHandler) Get() (interface{}, error) {
return role, nil return role, nil
} }
roles := make([]*models.Role, 0, 10) roles := make([]*models.Role, 0, 10)
err := cfg.DB().Preload("Auths").Preload("Users").Find(&roles).Error err := cfg.DB().Where("app_uuid = ?", h.uuid).Find(&roles).Error
return roles, err return roles, err
} }
@ -61,8 +71,7 @@ func (h *roleHandler) Patch() (interface{}, error) {
query := &struct { query := &struct {
Name *string `json:"name"` Name *string `json:"name"`
// 角色标签 // 角色标签
Tag *string `json:"tag" gorm:"default:''"` Tag *string `json:"tag" gorm:"default:''"`
IsUnique *bool `json:"is_unique" gorm:"default:false"`
}{} }{}
err := h.Meta().ReadJson(query) err := h.Meta().ReadJson(query)
if err != nil { if err != nil {
@ -92,15 +101,6 @@ func (h *roleHandler) Patch() (interface{}, error) {
return err return err
} }
} }
if query.IsUnique != nil && *query.IsUnique != role.IsUnique {
if *query.IsUnique && len(role.Users) > 1 {
return errors.New("该角色绑定用户已超过1个请解绑后在修改")
}
err = tx.Table("roles").Where("id = ?", role.ID).Update("is_unique", *query.IsUnique).Error
if err != nil {
return err
}
}
return err return err
}) })
} }

@ -0,0 +1,43 @@
package role
import (
"OneAuth/libs/auth"
"OneAuth/libs/base"
"OneAuth/libs/oerr"
"github.com/veypi/OneBD"
)
/**
* @name: roleAuth
* @author: veypi <i@veypi.com>
* @date: 2021-11-17 15:20
* @descriptionroleAuth
* @update: 2021-11-17 15:20
*/
var rap = OneBD.NewHandlerPool(func() OneBD.Handler {
return &roleAuthHandler{}
})
type roleAuthHandler struct {
base.ApiHandler
id uint
aid uint
uuid string
}
func (h *roleAuthHandler) Init(m OneBD.Meta) error {
id := uint(m.ParamsInt("id"))
aid := uint(m.ParamsInt("aid"))
if id == 0 || aid == 0 {
return oerr.ApiArgsError
}
return h.ApiHandler.Init(m)
}
func (h *roleAuthHandler) Post() (interface{}, error) {
if !h.Payload.GetAuth(auth.Auth, h.uuid).CanCreate() {
return nil, oerr.NoAuth
}
return nil, nil
}

@ -8,6 +8,7 @@ import (
func Router(r OneBD.Router) { func Router(r OneBD.Router) {
r.Set("/role/", roleP, rfc.MethodGet, rfc.MethodPost) r.Set("/role/", roleP, rfc.MethodGet, rfc.MethodPost)
r.Set("/role/:id", roleP, rfc.MethodGet, rfc.MethodDelete, rfc.MethodPatch) r.Set("/role/:id", roleP, rfc.MethodGet, rfc.MethodDelete, rfc.MethodPatch)
r.Set("/role/:id/:action/:rid", roleP, rfc.MethodGet) r.Set("/role/:id/auth/:aid", roleP, rfc.MethodGet)
r.Set("/auth/", authP, rfc.MethodGet) r.Set("/auth/", authP, rfc.MethodGet)
r.Set("/auth/:id", authP, rfc.MethodGet)
} }

@ -36,15 +36,16 @@ func (h *tokenHandler) Get() (interface{}, error) {
return nil, err return nil, err
} }
au := &models.AppUser{ au := &models.AppUser{
UserID: h.Payload.ID, UserID: h.Payload.ID,
AppID: a.ID, AppUUID: a.UUID,
} }
err = cfg.DB().Where(au).First(au).Error err = cfg.DB().Where(au).First(au).Error
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
if a.EnableRegister { if a.EnableRegister {
err = cfg.DB().Transaction(func(tx *gorm.DB) error { err = cfg.DB().Transaction(func(tx *gorm.DB) error {
return app.AddUser(cfg.DB(), au.AppID, au.UserID, a.InitRoleID, models.AUOK) _, err := app.AddUser(cfg.DB(), au.AppUUID, au.UserID, a.InitRoleID, models.AUOK)
return err
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -64,6 +65,6 @@ func (h *tokenHandler) Get() (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
t, err := token.GetToken(u, a.ID, a.Key) t, err := token.GetToken(u, a.UUID, a.Key)
return t, err return t, err
} }

@ -9,6 +9,7 @@ import (
"OneAuth/libs/token" "OneAuth/libs/token"
"OneAuth/models" "OneAuth/models"
"encoding/base64" "encoding/base64"
"errors"
"fmt" "fmt"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc" "github.com/veypi/OneBD/rfc"
@ -41,24 +42,24 @@ type handler struct {
// Get get user data // Get get user data
func (h *handler) Get() (interface{}, error) { func (h *handler) Get() (interface{}, error) {
if !h.Payload.GetAuth(auth.User, "").CanRead() {
return nil, oerr.NoAuth.AttachStr("to read user list")
}
username := h.Meta().Query("username")
if username != "" {
users := make([]*models.User, 0, 10)
err := cfg.DB().Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}
userID := h.Meta().ParamsInt("user_id") userID := h.Meta().ParamsInt("user_id")
if userID != 0 { if userID != 0 {
user := &models.User{} user := &models.User{}
user.ID = uint(userID) user.ID = uint(userID)
return user, cfg.DB().Where(user).First(user).Error return user, cfg.DB().Where(user).First(user).Error
} else { } else {
if !h.Payload.GetAuth(auth.User, "").CanRead() {
return nil, oerr.NoAuth.AttachStr("to read user list")
}
username := h.Meta().Query("username")
if username != "" {
users := make([]*models.User, 0, 10)
err := cfg.DB().Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
if err != nil {
return nil, err
}
return users, nil
}
users := make([]models.User, 10) users := make([]models.User, 10)
skip, err := strconv.Atoi(h.Meta().Query("skip")) skip, err := strconv.Atoi(h.Meta().Query("skip"))
if err != nil || skip < 0 { if err != nil || skip < 0 {
@ -120,7 +121,7 @@ func (h *handler) Post() (interface{}, error) {
if err := tx.Create(&h.User).Error; err != nil { if err := tx.Create(&h.User).Error; err != nil {
return oerr.ResourceDuplicated return oerr.ResourceDuplicated
} }
err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID, models.AUOK) _, err := app.AddUser(tx, self.UUID, h.User.ID, self.InitRoleID, models.AUOK)
if err != nil { if err != nil {
return err return err
} }
@ -137,6 +138,7 @@ func (h *handler) Patch() (interface{}, error) {
uid := h.Meta().Params("user_id") uid := h.Meta().Params("user_id")
opts := struct { opts := struct {
Password string `json:"password"` Password string `json:"password"`
Icon string `json:"icon"`
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
Phone string `json:"phone" gorm:"type:varchar(20);unique;default:null" json:",omitempty"` Phone string `json:"phone" gorm:"type:varchar(20);unique;default:null" json:",omitempty"`
Email string `json:"email" gorm:"type:varchar(50);unique;default:null" json:",omitempty"` Email string `json:"email" gorm:"type:varchar(50);unique;default:null" json:",omitempty"`
@ -167,6 +169,9 @@ func (h *handler) Patch() (interface{}, error) {
if opts.Nickname != "" { if opts.Nickname != "" {
target.Nickname = opts.Nickname target.Nickname = opts.Nickname
} }
if opts.Icon != "" {
target.Icon = opts.Icon
}
if opts.Position != "" { if opts.Position != "" {
target.Position = opts.Position target.Position = opts.Position
} }
@ -220,7 +225,7 @@ func (h *handler) Head() (interface{}, error) {
if err != nil { if err != nil {
return nil, oerr.DBErr.Attach(err) return nil, oerr.DBErr.Attach(err)
} }
if err := cfg.DB().Preload("Roles.Auths").Preload("Auths").Where(h.User).First(h.User).Error; err != nil { if err := cfg.DB().Where(h.User).First(h.User).Error; err != nil {
if err.Error() == gorm.ErrRecordNotFound.Error() { if err.Error() == gorm.ErrRecordNotFound.Error() {
return nil, oerr.AccountNotExist return nil, oerr.AccountNotExist
} else { } else {
@ -234,15 +239,27 @@ func (h *handler) Head() (interface{}, error) {
} }
au := &models.AppUser{} au := &models.AppUser{}
au.UserID = h.User.ID au.UserID = h.User.ID
au.AppID = target.ID au.AppUUID = target.UUID
err = cfg.DB().Where(au).First(au).Error err = cfg.DB().Where(au).First(au).Error
appID := target.ID
if err != nil { if err != nil {
return nil, err if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
if !target.EnableRegister {
return nil, errors.New("该应用不允许自主加入,请联系管理员")
}
_, err := app.AddUser(cfg.DB(), target.UUID, h.User.ID, target.InitRoleID, models.AUOK)
if err != nil {
return nil, err
}
} else if au.Status != models.AUOK { } else if au.Status != models.AUOK {
return nil, oerr.DisableLogin return nil, oerr.DisableLogin
} }
tokenStr, err := token.GetToken(h.User, appID, cfg.CFG.APPKey) err = h.User.LoadAuths(cfg.DB())
if err != nil {
return nil, err
}
tokenStr, err := token.GetToken(h.User, target.UUID, cfg.CFG.APPKey)
if err != nil { if err != nil {
log.HandlerErrs(err) log.HandlerErrs(err)
return nil, oerr.Unknown.Attach(err) return nil, oerr.Unknown.Attach(err)

@ -47,21 +47,10 @@ func (h *userRoleHandler) Post() (interface{}, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if query.IsUnique {
}
link := &models.UserRole{} link := &models.UserRole{}
link.UserID = uint(uid) link.UserID = uint(uid)
link.RoleID = query.ID link.RoleID = query.ID
err = cfg.DB().Transaction(func(tx *gorm.DB) (err error) { err = cfg.DB().Where(link).FirstOrCreate(link).Error
if query.IsUnique {
err = tx.Where("role_id = ?", query.ID).Delete(models.UserRole{}).Error
if err != nil {
return err
}
}
return tx.Where(link).FirstOrCreate(link).Error
})
return link, err return link, err
} }

@ -8,39 +8,42 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint, status models.AUStatus) error { func AddUser(tx *gorm.DB, uuid string, userID uint, roleID uint, status models.AUStatus) (*models.AppUser, error) {
if appID == 0 || userID == 0 { if uuid == "" || userID == 0 {
return oerr.FuncArgsError return nil, oerr.FuncArgsError
}
au := &models.AppUser{
AppUUID: uuid,
} }
au := &models.AppUser{}
au.AppID = appID
au.UserID = userID au.UserID = userID
err := tx.Where(au).First(au).Error err := tx.Where(au).First(au).Error
if err == nil { if err == nil {
return oerr.ResourceDuplicated return nil, oerr.ResourceDuplicated
} }
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
au.Status = status au.Status = status
err = tx.Create(au).Error err = tx.Create(au).Error
if err != nil { if err != nil {
return err return nil, err
} }
if roleID > 0 { if roleID > 0 {
err = auth.BindUserRole(tx, userID, roleID) err = auth.BindUserRole(tx, userID, roleID)
if err != nil { if err != nil {
return err return nil, err
} }
} }
return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error err = tx.Model(&models.App{}).Where("uuid = ?", uuid).Update("user_count", gorm.Expr("user_count + ?", 1)).Error
return au, err
} }
return err return nil, err
} }
func EnableUser(tx *gorm.DB, appID uint, userID uint) error { func EnableUser(tx *gorm.DB, uuid string, userID uint) error {
if appID == 0 || userID == 0 { if uuid == "" || userID == 0 {
return oerr.FuncArgsError return oerr.FuncArgsError
} }
au := &models.AppUser{} au := &models.AppUser{
au.AppID = appID AppUUID: uuid,
}
au.UserID = userID au.UserID = userID
err := tx.Where(au).First(au).Error err := tx.Where(au).First(au).Error
if err != nil { if err != nil {
@ -52,12 +55,13 @@ func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
return nil return nil
} }
func DisableUser(tx *gorm.DB, appID uint, userID uint) error { func DisableUser(tx *gorm.DB, uuid string, userID uint) error {
if appID == 0 || userID == 0 { if uuid == "" || userID == 0 {
return oerr.FuncArgsError return oerr.FuncArgsError
} }
au := &models.AppUser{} au := &models.AppUser{
au.AppID = appID AppUUID: uuid,
}
au.UserID = userID au.UserID = userID
return tx.Where(au).Update("status", models.AUDisable).Error return tx.Where(au).Update("status", models.AUDisable).Error
} }

@ -2,6 +2,7 @@ package auth
import ( import (
"OneAuth/models" "OneAuth/models"
"github.com/veypi/utils"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -10,6 +11,7 @@ import (
type Resource = string type Resource = string
const ( const (
// ruid 皆为app uuid
User Resource = "user" User Resource = "user"
APP Resource = "app" APP Resource = "app"
Res Resource = "resource" Res Resource = "resource"
@ -26,12 +28,12 @@ func BindUserRole(tx *gorm.DB, userID uint, roleID uint) error {
} }
ur := &models.UserRole{} ur := &models.UserRole{}
ur.RoleID = roleID ur.RoleID = roleID
if r.IsUnique { ur.UserID = userID
err = tx.Where(ur).Update("user_id", userID).Error err = utils.MultiErr(
} else { tx.Where(ur).FirstOrCreate(ur).Error,
ur.UserID = userID tx.Model(&models.Role{}).Where("id = ?", roleID).
err = tx.Where(ur).FirstOrCreate(ur).Error Update("user_count", gorm.Expr("user_count + ?", 1)).Error,
} )
return err return err
} }
@ -51,7 +53,7 @@ func bind(tx *gorm.DB, id uint, resID uint, level models.AuthLevel, ruid string,
return err return err
} }
au := &models.Auth{ au := &models.Auth{
AppID: r.AppID, AppUUID: r.AppUUID,
ResourceID: resID, ResourceID: resID,
RID: r.Name, RID: r.Name,
RUID: ruid, RUID: ruid,

@ -2,8 +2,8 @@ package key
import "OneAuth/cfg" import "OneAuth/cfg"
func App(id uint) string { func App(uuid string) string {
if id == cfg.CFG.APPID { if uuid == cfg.CFG.APPUUID {
return cfg.CFG.APPKey return cfg.CFG.APPKey
} }
// TODO // TODO

@ -8,8 +8,8 @@ import (
var keyCache = sync.Map{} var keyCache = sync.Map{}
func User(uid uint, appID uint) string { func User(uid uint, uuid string) string {
if appID == cfg.CFG.APPID { if uuid == cfg.CFG.APPUUID {
key, _ := keyCache.LoadOrStore(uid, utils.RandSeq(16)) key, _ := keyCache.LoadOrStore(uid, utils.RandSeq(16))
return cfg.CFG.APPKey + key.(string) return cfg.CFG.APPKey + key.(string)
} }
@ -17,8 +17,8 @@ func User(uid uint, appID uint) string {
return "" return ""
} }
func RefreshUser(uid uint, appID uint) string { func RefreshUser(uid uint, uuid string) string {
if appID == cfg.CFG.APPID { if uuid == cfg.CFG.APPUUID {
key := utils.RandSeq(16) key := utils.RandSeq(16)
keyCache.Store(uid, key) keyCache.Store(uid, key)
return key return key

@ -5,7 +5,7 @@ import (
"github.com/veypi/utils/jwt" "github.com/veypi/utils/jwt"
) )
type simpleAuth struct { type SimpleAuth struct {
RID string `json:"rid"` RID string `json:"rid"`
// 具体某个资源的id // 具体某个资源的id
RUID string `json:"ruid"` RUID string `json:"ruid"`
@ -15,8 +15,8 @@ type simpleAuth struct {
// TODO:: roles 是否会造成token过大 ? // TODO:: roles 是否会造成token过大 ?
type PayLoad struct { type PayLoad struct {
jwt.Payload jwt.Payload
ID uint `json:"id"` ID uint `json:"id"`
Auth map[uint]*simpleAuth `json:"auth"` Auth []*SimpleAuth `json:"auth"`
} }
// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖 // GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖
@ -47,18 +47,18 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.Auth
return res return res
} }
func GetToken(u *models.User, appID uint, key string) (string, error) { func GetToken(u *models.User, uuid string, key string) (string, error) {
payload := &PayLoad{ payload := &PayLoad{
ID: u.ID, ID: u.ID,
Auth: map[uint]*simpleAuth{}, Auth: []*SimpleAuth{},
} }
for _, a := range u.GetAuths() { for _, a := range u.GetAuths() {
if appID == a.AppID { if uuid == a.AppUUID {
payload.Auth[a.ID] = &simpleAuth{ payload.Auth = append(payload.Auth, &SimpleAuth{
RID: a.RID, RID: a.RID,
RUID: a.RUID, RUID: a.RUID,
Level: a.Level, Level: a.Level,
} })
} }
} }
return jwt.GetToken(payload, []byte(key)) return jwt.GetToken(payload, []byte(key))

@ -3,14 +3,16 @@ package models
var AppKeys = map[string]string{} var AppKeys = map[string]string{}
type App struct { type App struct {
BaseModel UUID string `json:"uuid" gorm:"primaryKey;size:32"`
Name string `json:"name"` CreatedAt JSONTime `json:"created_at"`
Icon string `json:"icon"` UpdatedAt JSONTime `json:"updated_at"`
UUID string `json:"uuid" gorm:"unique"` DeletedAt *JSONTime `json:"deleted_at" sql:"index"`
Des string `json:"des"` Name string `json:"name"`
Creator uint `json:"creator"` Icon string `json:"icon"`
UserCount uint `json:"user_count"` Des string `json:"des"`
Users []*User `json:"users" gorm:"many2many:app_users;"` Creator uint `json:"creator"`
UserCount uint `json:"user_count"`
Users []*User `json:"users" gorm:"many2many:app_users;"`
// 初始用户角色 // 初始用户角色
InitRoleID uint `json:"init_role_id"` InitRoleID uint `json:"init_role_id"`
InitRole *Role `json:"init_role"` InitRole *Role `json:"init_role"`
@ -35,11 +37,12 @@ type App struct {
EnableUserKey bool `json:"enable_user_key"` EnableUserKey bool `json:"enable_user_key"`
UserKeyUrl string `json:"user_key_url"` UserKeyUrl string `json:"user_key_url"`
// 允许登录方式 // 允许登录方式
EnableUser bool `json:"enable_user"` EnableUser bool `json:"enable_user"`
EnableWx bool `json:"enable_wx"` EnableWx bool `json:"enable_wx"`
EnablePhone bool `json:"enable_phone"` EnablePhone bool `json:"enable_phone"`
EnableEmail bool `json:"enable_email"` EnableEmail bool `json:"enable_email"`
Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"`
Wx *Wechat `json:"wx" gorm:"-"`
} }
type AUStatus string type AUStatus string
@ -53,16 +56,17 @@ const (
type AppUser struct { type AppUser struct {
BaseModel BaseModel
AppID uint `json:"app_id"` AppUUID string `json:"app_uuid" gorm:"size:32"`
APP *App `json:"app"` App *App `json:"app" gorm:"association_foreignkey:UUID"`
UserID uint `json:"user_id"` UserID uint `json:"user_id"`
User *User `json:"user"` User *User `json:"user"`
Status AUStatus `json:"status"` Status AUStatus `json:"status"`
} }
type Wechat struct { type Wechat struct {
BaseModel BaseModel
AppID uint `json:"app_id"` AppUUID string `json:"app_uuid" gorm:"size:32"`
App *App `json:"app" gorm:"association_foreignkey:UUID"`
// 网页授权登录用 // 网页授权登录用
WxID string `json:"wx_id"` WxID string `json:"wx_id"`
AgentID string `json:"agent_id"` AgentID string `json:"agent_id"`

@ -95,7 +95,7 @@ func (jt *JSONTime) SetTime(t time.Time) {
} }
type BaseModel struct { type BaseModel struct {
ID uint `json:"id" gorm:"primary_key"` ID uint `json:"id" gorm:"primaryKey"`
CreatedAt JSONTime `json:"created_at"` CreatedAt JSONTime `json:"created_at"`
UpdatedAt JSONTime `json:"updated_at"` UpdatedAt JSONTime `json:"updated_at"`
DeletedAt *JSONTime `json:"deleted_at" sql:"index"` DeletedAt *JSONTime `json:"deleted_at" sql:"index"`

@ -8,15 +8,15 @@ type UserRole struct {
type Role struct { type Role struct {
BaseModel BaseModel
AppID uint `json:"app_id"` AppUUID string `json:"app_uuid" gorm:"size:32"`
App *App `json:"app"` App *App `json:"app" gorm:"association_foreignkey:UUID"`
Name string `json:"name"` Name string `json:"name"`
// 角色标签 // 角色标签
Tag string `json:"tag" gorm:"default:''"` Tag string `json:"tag" gorm:"default:''"`
Users []*User `json:"users" gorm:"many2many:user_roles;"` Users []*User `json:"users" gorm:"many2many:user_roles;"`
// 具体权限 // 具体权限
Auths []*Auth `json:"auths" gorm:"foreignkey:RoleID;references:ID"` Auths []*Auth `json:"auths" gorm:"foreignkey:RoleID;references:ID"`
IsUnique bool `json:"is_unique" gorm:"default:false"` UserCount uint `json:"user_count"`
} }
// AuthLevel 权限等级 // AuthLevel 权限等级
@ -77,8 +77,8 @@ func (a AuthLevel) CanDoAny() bool {
type Auth struct { type Auth struct {
BaseModel BaseModel
// 该权限作用的应用 // 该权限作用的应用
AppID uint `json:"app_id"` AppUUID string `json:"app_uuid" gorm:"size:32"`
App *App `json:"app"` App *App `json:"app" gorm:"association_foreignkey:UUID"`
// 权限绑定只能绑定一个 // 权限绑定只能绑定一个
RoleID *uint `json:"role_id" gorm:""` RoleID *uint `json:"role_id" gorm:""`
Role *Role `json:"role"` Role *Role `json:"role"`
@ -96,9 +96,9 @@ type Auth struct {
type Resource struct { type Resource struct {
BaseModel BaseModel
AppID uint `json:"app_id"` AppUUID string `json:"app_uuid" gorm:"size:32"`
App *App `json:"app"` App *App `json:"app" gorm:"association_foreignkey:UUID"`
Name string `json:"name"` Name string `json:"name"`
// 权限标签 // 权限标签
Tag string `json:"tag"` Tag string `json:"tag"`
Des string `json:"des"` Des string `json:"des"`

@ -2,6 +2,7 @@ package models
import ( import (
"github.com/veypi/utils" "github.com/veypi/utils"
"gorm.io/gorm"
) )
// User db user model // User db user model
@ -27,6 +28,10 @@ func (u *User) String() string {
return u.Username + ":" + u.Nickname return u.Username + ":" + u.Nickname
} }
func (u *User) LoadAuths(tx *gorm.DB) error {
return tx.Where("id = ?", u.ID).Preload("Auths").Preload("Roles.Auths").First(u).Error
}
func (u *User) GetAuths() []*Auth { func (u *User) GetAuths() []*Auth {
list := make([]*Auth, 0, 10) list := make([]*Auth, 0, 10)
for _, r := range u.Roles { for _, r := range u.Roles {
@ -40,14 +45,14 @@ func (u *User) GetAuths() []*Auth {
return list return list
} }
func (u *User) GetAuth(appID uint, ResourceID string, ResourceUUID ...string) AuthLevel { func (u *User) GetAuth(uuid, ResourceID string, ResourceUUID ...string) AuthLevel {
var res = AuthNone var res = AuthNone
ruid := "" ruid := ""
if len(ResourceUUID) > 0 { if len(ResourceUUID) > 0 {
ruid = ResourceUUID[0] ruid = ResourceUUID[0]
} }
for _, a := range u.GetAuths() { for _, a := range u.GetAuths() {
if a.RID == ResourceID && a.AppID == appID { if a.RID == ResourceID && a.AppUUID == uuid {
if a.RUID != "" { if a.RUID != "" {
if a.RUID == ruid { if a.RUID == ruid {
if a.Level.Upper(res) { if a.Level.Upper(res) {

@ -1,13 +1,92 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite App</title> <title>Vite App</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.ts"></script> <div id="loader-wrapper">
</body> <div class="lds-ripple">
<div></div>
<div></div>
</div>
<div class="load_title">Loading...
</div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
<style>
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
background: #999;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 19px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 70%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #FFF;
opacity: 0.5;
}
.lds-ripple {
display: inline-block;
position: relative;
left: calc(50% - 40px);
top: calc(70% - 100px);
width: 80px;
height: 80px;
}
.lds-ripple div {
position: absolute;
border: 4px solid #fff;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 72px;
height: 72px;
opacity: 0;
}
}
</style>
</html> </html>

@ -1,16 +1,18 @@
{ {
"name": "oaf", "name": "oaf",
"version": "0.1.0", "version": "0.1.0",
"license": "Apache-2.0",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vue-tsc --noEmit && vite build", "build": "vue-tsc --noEmit && vite build",
"serve": "vite preview" "serve": "vite preview"
}, },
"dependencies": { "dependencies": {
"@veypi/one-icon": "2.0.5", "@veypi/one-icon": "2.0.6",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"seamless-scroll-polyfill": "^2.1.5",
"vue": "^3.2.16", "vue": "^3.2.16",
"vue-router": "^4.0.12", "vue-router": "^4.0.12",
"vuex": "^4.0.2" "vuex": "^4.0.2"

@ -1,89 +1,34 @@
<template> <template>
<n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN" <base-frame>
:theme="Theme"> <router-view v-slot="{ Component }">
<n-layout class="font-sans select-none"> <transition mode="out-in" enter-active-class="animate__fadeInLeft"
<n-layout> leave-active-class="animate__fadeOutRight">
<n-layout-header class="pr-5" bordered style="height: 64px;line-height: 64px;"> <component class="animate__animated animate__400ms" :is="Component"
<div class="inline-block float-left h-full"> style="margin: 10px; min-height: calc(100vh - 108px)"
<one-icon color="#000" class="inline-block" @click="$router.push('/')" style="font-size: 48px;margin:8px;color:aqua"> ></component>
glassdoor </transition>
</one-icon> </router-view>
</div> </base-frame>
<div class="inline-block float-left h-full" style="margin-left: 10px">
<n-h6 prefix="bar" align-text><n-text type="primary">统一认证系统</n-text></n-h6>
</div>
<div v-if="store.state.user.ready" class="inline-block h-full float-right flex justify-center items-center">
<avatar></avatar>
</div>
<div class="inline-block float-right h-full px-3">
<fullscreen v-model="isFullScreen" class="header-icon">fullscreen</fullscreen>
<div class="header-icon">
<one-icon @click="ChangeTheme">
{{ IsDark ? 'Daytimemode' : 'nightmode-fill' }}
</one-icon>
</div>
</div>
</n-layout-header>
<n-layout has-sider style="height: calc(100vh - 88px)">
<n-layout-sider
collapse-mode="transform"
:collapsed-width="0"
:width="120"
show-trigger="bar"
content-style="padding: 24px;"
bordered
default-collapsed
:native-scrollbar="false"
>
-
</n-layout-sider>
<n-layout class="main" :native-scrollbar="false">
<n-message-provider>
<router-view v-slot="{ Component }">
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
<component class="animate__animated animate__400ms" :is="Component" style="margin: 10px; min-height: calc(100vh - 108px)"
></component>
</transition>
</router-view>
</n-message-provider>
</n-layout>
</n-layout>
</n-layout>
<n-layout-footer bordered style="height: 24px;line-height: 24px"
class="flex justify-around px-3 text-gray-500 text-xs">
<span class="hover:text-black cursor-pointer" @click="$router.push({name: 'about'})">关于OA</span>
<span class="hover:text-black cursor-pointer">使用须知</span>
<span class="hover:text-black cursor-pointer" @click="goto('https://veypi.com')">
©2021 veypi
</span>
</n-layout-footer>
</n-layout>
</n-config-provider>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs // This starter template is using Vue 3 <script setup> SFCs
import BaseFrame from './components/frame.vue'
import {onBeforeMount, ref} from 'vue' import {onBeforeMount, ref} from 'vue'
import util from './libs/util' import util from './libs/util'
import {useStore} from "./store"; import {useStore} from "./store";
import {Theme, IsDark, ChangeTheme} from "./theme";
import {zhCN, dateZhCN} from 'naive-ui'
import avatar from "./components/avatar";
import fullscreen from './components/fullscreen'
import Fullscreen from "./components/fullscreen/fullscreen.vue";
let isFullScreen = ref(false)
let store = useStore() let store = useStore()
onBeforeMount(() => { onBeforeMount(() => {
let loader = document.getElementById("loader-wrapper")
if (loader && loader.parentElement) {
loader.parentElement.removeChild(loader)
}
util.title("统一认证") util.title("统一认证")
store.dispatch('fetchSelf') store.dispatch('fetchSelf')
store.dispatch('user/fetchUserData') store.dispatch('user/fetchUserData')
}) })
let goto = (url: any) => {
window.open(url, "_blank")
}
let collapsed = ref(true) let collapsed = ref(true)
</script> </script>
@ -94,6 +39,14 @@ let collapsed = ref(true)
--animate-duration: 400ms; --animate-duration: 400ms;
} }
.page-h1 {
font-size: 1.5rem;
line-height: 2rem;
margin-left: 2.5rem;
margin-top: 1.25rem;
margin-bottom: 1.25rem;
}
html, html,
body { body {
width: 100%; width: 100%;
@ -128,9 +81,6 @@ body {
height: 100%; height: 100%;
} }
.main {
}
::-webkit-scrollbar { ::-webkit-scrollbar {
display: none; /* Chrome Safari */ display: none; /* Chrome Safari */
} }

@ -1,6 +1,5 @@
// @ts-ignore
import axios from 'axios' import axios from 'axios'
import {store} from '../store' import {store} from '@/store'
function baseRequests(url: string, method: any = 'GET', query: any, data: any, success: any, fail?: Function) { function baseRequests(url: string, method: any = 'GET', query: any, data: any, success: any, fail?: Function) {

@ -0,0 +1,40 @@
/*
* @name: app
* @author: veypi <i@veypi.com>
* @date: 2021-11-17 14:44
* @descriptionap
* @update: 2021-11-17 14:44
*/
import {Interface} from './interface'
import ajax from './ajax'
import {BaseUrl} from './setting'
export default {
local: BaseUrl + 'app/',
self() {
return new Interface(ajax.get, this.local, {option: 'oa'})
},
create(name: string) {
return new Interface(ajax.post, this.local, {name: name})
},
get(uuid: string) {
return new Interface(ajax.get, this.local + uuid)
},
list() {
return new Interface(ajax.get, this.local)
},
update(uuid: string, props: any) {
return new Interface(ajax.patch, this.local + uuid, props)
},
user(uuid: string) {
if (uuid === '') {
uuid = '-'
}
return {
local: this.local + uuid + '/user/',
list(uid: number) {
return new Interface(ajax.get, this.local + uid)
}
}
}
}

@ -0,0 +1,16 @@
import {Interface} from './interface'
import ajax from './ajax'
import {BaseUrl} from './setting'
export default (uuid: string) => {
return {
local: BaseUrl + 'app/' + uuid + '/auth/',
get(id: number) {
return new Interface(ajax.get, this.local + id)
},
list(appUUID: string, user_id: number) {
return new Interface(ajax.get, this.local, {uuid: appUUID, id: user_id})
},
}
}

@ -1,112 +1,21 @@
/* /*
* Copyright (C) 2019 light <light@light-laptop> * Copyright (C) 2019 light <veypi@light-laptop>
* *
* Distributed under terms of the MIT license. * Distributed under terms of the MIT license.
*/ */
import {App} from 'vue' import {App} from 'vue'
import ajax from './ajax' import role from "./role";
import {store} from '../store' import app from './app'
import {Base64} from 'js-base64' import user from './user'
import auth from './auth'
export type SuccessFunction<T> = (e: any) => void;
export type FailedFunction<T> = (e: any) => void;
const Code = {
42011: '无操作权限',
22031: '资源不存在 或 您无权操作该资源'
}
class Interface {
private readonly method: Function
private readonly api: string
private readonly data: any
constructor(method: Function, api: string, data?: any) {
this.method = method
this.api = api
this.data = data
}
Start(success: SuccessFunction<any>, fail?: FailedFunction<any>) {
const newFail = function (data: any) {
if (data && data.code === 40001) {
// no login
store.commit('user/logout')
return
}
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
if (data && data.code && Code[data.code]) {
}
if (fail) {
fail(data.err)
}
}
const newSuccess = function (data: any) {
if (Number(data.status) === 1) {
if (success) {
success(data.content)
}
} else {
newFail(data)
if (data.code === 41001) {
store.commit('user/logout')
// bus.$emit('log_out')
}
}
}
this.method(this.api, this.data, newSuccess, newFail)
}
}
const app = {
local: '/api/app/',
self() {
return new Interface(ajax.get, this.local, {is_self: true})
},
get(id: string) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
}
}
const user = {
local: '/api/user/',
register(username: string, password: string, uuid: string, prop?: any) {
const data = Object.assign({
username: username,
uuid: uuid,
password: Base64.encode(password)
}, prop)
return new Interface(ajax.post, this.local, data)
},
login(username: string, password: string, uuid: string) {
return new Interface(ajax.head, this.local + username, {
uid_type: 'username',
uuid: uuid,
password: Base64.encode(password)
})
},
get(id: number) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
},
update(id: number, props: any) {
return new Interface(ajax.patch, this.local + id, props)
}
}
const api = { const api = {
user: user, user: user,
app: app app: app,
auth: auth,
role: role
} }
const Api = { const Api = {

@ -0,0 +1,59 @@
import {store} from "@/store";
export type SuccessFunction<T> = (e: any) => void;
export type FailedFunction<T> = (e: any) => void;
const Code = {
42011: '无操作权限',
22031: '资源不存在 或 您无权操作该资源'
}
export class Interface {
private readonly method: Function
private readonly api: string
private readonly data: any
constructor(method: Function, api: string, data?: any) {
this.method = method
this.api = api
this.data = data
}
Start(success: SuccessFunction<any>, fail?: FailedFunction<any>) {
const newFail = function (data: any) {
if (data) {
if (data.code === 40001) {
// no login
store.commit('user/logout')
return
// @ts-ignore
} else if (data.code === 42011 && window.$msg) {
// @ts-ignore
window.$msg.warning('无权限')
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
if (data && data.code && Code[data.code]) {
}
if (fail) {
fail(data.err)
}
}
const newSuccess = function (data: any) {
if (Number(data.status) === 1) {
if (success) {
success(data.content)
}
} else {
newFail(data)
if (data.code === 41001) {
store.commit('user/logout')
// bus.$emit('log_out')
}
}
}
this.method(this.api, this.data, newSuccess, newFail)
}
}

@ -0,0 +1,23 @@
import {BaseUrl} from './setting'
import {Interface} from './interface'
import ajax from './ajax'
export default (uuid: string) => {
return {
local: BaseUrl +'app/' + uuid + '/role/',
get(id: number) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
},
create(uuid: string, name: string) {
return new Interface(ajax.post, this.local, {
uuid: uuid,
name: name,
})
},
bind(id: number, aid: number) {
},
}
}

@ -0,0 +1,10 @@
/*
* @name: setting
* @author: veypi <i@veypi.com>
* @date: 2021-11-17 15:45
* @descriptionsetting
* @update: 2021-11-17 15:45
*/
export const BaseUrl = '/api/'

@ -0,0 +1,31 @@
import {Base64} from "js-base64";
import {Interface} from './interface'
import ajax from './ajax'
import {BaseUrl} from './setting'
export default {
local: BaseUrl + 'user/',
register(username: string, password: string, prop?: any) {
const data = Object.assign({
username: username,
password: Base64.encode(password)
}, prop)
return new Interface(ajax.post, this.local, data)
},
login(username: string, password: string, uuid: string) {
return new Interface(ajax.head, this.local + username, {
uid_type: 'username',
uuid: uuid,
password: Base64.encode(password)
})
},
get(id: number) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
},
update(id: number, props: any) {
return new Interface(ajax.patch, this.local + id, props)
}
}

@ -0,0 +1,77 @@
export const R = {
// 应用管理配置权限
App: 'app',
// 用户管理和绑定应用权限
User: 'user',
// 权限资源定义权限
Resource: 'resource',
// 角色管理和绑定用户权限
Role: 'role',
// 权限管理和绑定角色权限
Auth: 'auth',
}
const level = {
None: 0,
Do: 1,
Part: 1,
Read: 2,
Create: 3,
Update: 4,
Delete: 5,
All: 6
}
export interface Auth {
rid: string
ruid: string
level: number
}
class authLevel {
level = level.None
constructor(level: number) {
this.level = level
}
CanDo(): boolean {
return this.level >= level.Do
}
CanRead(): boolean {
return this.level >= level.Read
}
CanCreate(): boolean {
return this.level >= level.Create
}
CanUpdate(): boolean {
return this.level >= level.Update
}
CanDelete(): boolean {
return this.level >= level.Delete
}
CanDoAny(): boolean {
return this.level >= level.All
}
}
export class Auths {
private readonly list: [Auth]
constructor(auths: [Auth]) {
this.list = auths
}
Get(rid: string, ruid: string): authLevel {
let l = level.None
for (let i of this.list) {
if (i.rid == rid && (i.ruid === '' || i.ruid === ruid) && i.level > l) {
l = i.level
}
}
return new authLevel(l)
}
}
export function NewAuths(a: any) {
return new Auths(a)
}

@ -2,8 +2,7 @@
<div class="core rounded-2xl p-3"> <div class="core rounded-2xl p-3">
<div class="grid gap-4 grid-cols-5"> <div class="grid gap-4 grid-cols-5">
<div class="col-span-2"> <div class="col-span-2">
<n-avatar @click="$router.push({name: 'app', params: {uuid: core.uuid}})" round :size="80" :src="core.icon"> <n-avatar @click="$router.push({name: 'app.main', params: {uuid: core.uuid}})" round :size="80" :src="core.icon">
{{ core.icon ? '' : core.name }}
</n-avatar> </n-avatar>
</div> </div>
<div class="col-span-3 grid grid-cols-1 items-center text-left"> <div class="col-span-3 grid grid-cols-1 items-center text-left">
@ -15,15 +14,21 @@
</div> </div>
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
import {defineProps} from "vue"; import {defineProps, withDefaults} from "vue";
interface App {
let props = defineProps<{ uuid: ''
core: any [key: string]: any
}>() }
let props = withDefaults(defineProps<{
core?: App
}>(), {
// @ts-ignore
core: {}
})
</script> </script>
<style scoped> <style scoped>
.core { .core {
width: 256px; width: 256px;
background: #2c3e50; background: rgba(146, 145, 145, 0.1);
} }
</style> </style>

@ -1,5 +1,5 @@
<template> <template>
<base_frame style="line-height:40px" v-model="shown" :isDark="IsDark"> <base-frame style="line-height:40px" v-model="shown" :isDark="IsDark">
<div class="flex"> <div class="flex">
<n-avatar :src="$store.state.user.icon" round></n-avatar> <n-avatar :src="$store.state.user.icon" round></n-avatar>
</div> </div>
@ -40,20 +40,19 @@
</div> </div>
</div> </div>
</template> </template>
</base_frame> </base-frame>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import base_frame from './frame.vue' import BaseFrame from './frame.vue'
import {IsDark} from '../../theme' import {IsDark} from '@/theme'
import {ref} from "vue"; import {ref} from "vue";
import {useMessage} from 'naive-ui'
// @ts-ignore
window.$msg = useMessage()
let shown = ref(false) let shown = ref(false)
function asd(e) {
console.log([e, shown.value])
}
</script> </script>
<style scoped> <style scoped>

@ -28,7 +28,6 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { defineProps, defineEmits, watch} from "vue";
let emits = defineEmits<{ let emits = defineEmits<{
(e: 'update:modelValue', v: boolean): void (e: 'update:modelValue', v: boolean): void

@ -1,8 +1,68 @@
<template> <template>
<div></div> <n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN"
:theme="Theme">
<n-message-provider>
<n-layout class="font-sans select-none">
<n-layout>
<n-layout-header class="pr-5" bordered style="height: 64px;line-height: 64px;">
<div class="inline-block float-left h-full">
<one-icon color="#000" class="inline-block" @click="$router.push('/')"
style="font-size: 48px;margin:8px;color:aqua">
glassdoor
</one-icon>
</div>
<div class="inline-block float-left h-full" style="margin-left: 10px">
<n-h6 prefix="bar" align-text>
<n-text type="primary">统一认证系统</n-text>
</n-h6>
</div>
<div v-if="$store.state.user.ready"
class="inline-block h-full float-right flex justify-center items-center">
<avatar></avatar>
</div>
<div class="inline-block float-right h-full px-3">
<fullscreen v-model="isFullScreen" class="header-icon">fullscreen</fullscreen>
<div class="header-icon">
<one-icon @click="ChangeTheme">
{{ IsDark ? 'Daytimemode' : 'nightmode-fill' }}
</one-icon>
</div>
</div>
</n-layout-header>
<n-layout style="height: calc(100vh - 88px)" :native-scrollbar="false">
<slot></slot>
<n-back-top>
</n-back-top>
</n-layout>
</n-layout>
<n-layout-footer bordered style="height: 24px;line-height: 24px"
class="flex justify-around px-3 text-gray-500 text-xs">
<span class="hover:text-black cursor-pointer" @click="$router.push({name: 'about'})">关于OA</span>
<span class="hover:text-black cursor-pointer">使用须知</span>
<span class="hover:text-black cursor-pointer" @click="goto('https://veypi.com')">
©2021 veypi
</span>
</n-layout-footer>
</n-layout>
</n-message-provider>
</n-config-provider>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {Theme, IsDark, ChangeTheme} from "@/theme";
import {zhCN, dateZhCN} from 'naive-ui'
import fullscreen from './fullscreen'
import avatar from './avatar'
import {onMounted, ref} from "vue";
let goto = (url: any) => {
window.open(url, "_blank")
}
let isFullScreen = ref(false)
onMounted(() => {
})
</script> </script>
<style scoped> <style scoped>

@ -5,8 +5,8 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
// @ts-nocheck
import {defineEmits, onMounted, defineProps} from "vue"; import {onMounted} from "vue";
let emit = defineEmits<{ let emit = defineEmits<{
(e: 'update:modelValue', v: boolean): void (e: 'update:modelValue', v: boolean): void

@ -0,0 +1,29 @@
<template>
<n-layout has-sider>
<n-layout-sider
collapse-mode="transform"
:collapsed-width="0"
:width="150"
show-trigger="bar"
content-style="padding: 4px;"
bordered
default-collapsed
:native-scrollbar="false"
style="height: calc(100vh - 108px)"
>
<slot name="sider"></slot>
</n-layout-sider>
<n-layout style="height: calc(100vh - 108px);padding-left: 20px" :native-scrollbar="false">
<slot></slot>
<n-back-top>
</n-back-top>
</n-layout>
</n-layout>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>

@ -1,12 +1,16 @@
import {createRouter, createWebHistory} from 'vue-router' import {createRouter, createWebHistory} from 'vue-router'
import util from '../libs/util' import util from '@/libs/util'
import {Auths, R} from '@/auth'
import {store} from "@/store";
declare module 'vue-router' { declare module 'vue-router' {
interface RouteMeta { interface RouteMeta {
// 是可选的 // 是可选的
isAdmin?: boolean isAdmin?: boolean
title?: string
// 每个路由都必须声明 // 每个路由都必须声明
requiresAuth: boolean requiresAuth: boolean
checkAuth?: (a: Auths) => boolean
} }
} }
@ -19,15 +23,59 @@ const router = createRouter({
meta: { meta: {
requiresAuth: true, requiresAuth: true,
}, },
component: () => import('../views/home.vue') component: () => import('@/views/home.vue')
}, },
{ {
path: '/app/:uuid?', path: '/app/:uuid?',
name: 'app', component: () => import('@/views/app.vue'),
meta: { redirect: {name: 'app.main'},
requiresAuth: true, children: [
}, {
component: () => import('../views/app.vue') path: 'main',
name: 'app.main',
meta: {
title: '首页',
requiresAuth: true,
},
component: () => import('@/views/app/main.vue')
},
{
path: 'users',
name: 'app.users',
meta: {
title: '用户',
requiresAuth: true,
checkAuth: a => {
return a.Get(R.User, '').CanRead()
}
},
component: () => import('@/views/app/users.vue')
},
{
path: 'roles',
name: 'app.roles',
meta: {
title: '权限',
requiresAuth: true,
checkAuth: a => {
return a.Get(R.Role, '').CanRead()
}
},
component: () => import('@/views/app/roles.vue')
},
{
path: 'setting',
name: 'app.setting',
meta: {
title: '应用设置',
requiresAuth: true,
checkAuth: a => {
return a.Get(R.App, '').CanRead()
}
},
component: () => import('@/views/app/setting.vue')
}
]
}, },
{ {
path: '/user/setting', path: '/user/setting',
@ -35,39 +83,37 @@ const router = createRouter({
meta: { meta: {
requiresAuth: true requiresAuth: true
}, },
component: () => import('../views/user_setting.vue') component: () => import('@/views/user_setting.vue')
}, },
{ {
path: '/about', path: '/about',
name: 'about', name: 'about',
component: () => import('../views/about.vue') component: () => import('@/views/about.vue')
}, },
{ {
path: '/wx', path: '/wx',
name: 'wx', name: 'wx',
component: () => import('../views/wx.vue') component: () => import('@/views/wx.vue')
}, },
{ {
path: '/login/:uuid?', path: '/login/:uuid?',
name: 'login', name: 'login',
component: () => import('../views/login.vue') component: () => import('@/views/login.vue')
}, },
{ {
path: '/register/:uuid?', path: '/register/:uuid?',
name: 'register', name: 'register',
component: () => import('../views/register.vue') component: () => import('@/views/register.vue')
}, },
{ {
path: '/:path(.*)', path: '/:path(.*)',
name: '404', name: '404',
component: () => import('../views/404.vue') component: () => import('@/views/404.vue')
} }
//...
], ],
}) })
router.beforeEach((to, from) => { router.beforeEach((to, from) => {
// 而不是去检查每条路由记录
// to.matched.some(record => record.meta.requiresAuth) // to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !util.checkLogin()) { if (to.meta.requiresAuth && !util.checkLogin()) {
// 此路由需要授权,请检查是否已登录 // 此路由需要授权,请检查是否已登录
@ -78,6 +124,17 @@ router.beforeEach((to, from) => {
query: {redirect: to.fullPath}, query: {redirect: to.fullPath},
} }
} }
if (to.meta.checkAuth) {
if (!to.meta.checkAuth(store.state.user.auth)) {
// @ts-ignore
if (window.$msg) {
// @ts-ignore
window.$msg.warning('无权访问')
}
return from
}
}
}) })
export default router export default router

@ -1,6 +1,6 @@
import {InjectionKey} from 'vue' import {InjectionKey} from 'vue'
import {createStore, useStore as baseUseStore, Store} from 'vuex' import {createStore, useStore as baseUseStore, Store} from 'vuex'
import api from "../api"; import api from "@/api";
import {User, UserState} from './user' import {User, UserState} from './user'
export interface State extends Object { export interface State extends Object {

@ -1,9 +1,10 @@
import {Module} from "vuex"; import {Module} from "vuex";
import api from "../api"; import api from "@/api";
import util from '../libs/util' import util from '@/libs/util'
import {Base64} from 'js-base64' import {Base64} from 'js-base64'
import {State} from './index' import {State} from './index'
import router from "../router"; import router from "@/router";
import {Auths, NewAuths} from '@/auth'
export interface UserState { export interface UserState {
id: number id: number
@ -13,17 +14,11 @@ export interface UserState {
icon: string icon: string
email: string email: string
ready: boolean ready: boolean
auth: [auth?] auth: Auths
[key: string]: any [key: string]: any
} }
interface auth {
rid: string
ruid: string
level: number
}
export const User: Module<UserState, State> = { export const User: Module<UserState, State> = {
namespaced: true, namespaced: true,
state: { state: {
@ -33,7 +28,7 @@ export const User: Module<UserState, State> = {
phone: '', phone: '',
icon: '', icon: '',
email: '', email: '',
auth: [], auth: NewAuths([]),
ready: false ready: false
}, },
mutations: { mutations: {
@ -47,7 +42,7 @@ export const User: Module<UserState, State> = {
state.ready = true state.ready = true
}, },
setAuth(state: UserState, data: any) { setAuth(state: UserState, data: any) {
state.auth = data state.auth = NewAuths(data)
}, },
logout(state: UserState) { logout(state: UserState) {
state.ready = false state.ready = false

@ -1,26 +1,98 @@
<template> <template>
<div> <siderframe>
{{ uuid }} <template v-slot:sider>
</div> <div class="grid grid-cols-1">
<div class="cursor-pointer" v-for="(item, key) in navRouter" :key="key">
<div class="pl-4 text-lg my-4" :style="{color: isEqualRoute(item) ? '#88baea': '#888'}"
@click="router.push(Object.assign({}, item, {params: route.params, query: route.query}))">
{{ item.meta.title }}
</div>
<template v-if="isEqualRoute(item) && nav && nav.length>0">
<transition-group appear enter-active-class="animate__zoomIn" leave-active-class="animate__zoomOut">
<div @click="goAnchor(tt)" class="pl-8 rounded my-0.5 animate__animated animate__400ms"
v-for="(tt, kk) in nav" :key="kk">
{{ tt.innerText }}
</div>
</transition-group>
</template>
</div>
</div>
</template>
<router-view v-slot="{ Component }">
<transition mode="out-in" enter-active-class="animate__fadeInLeft"
leave-active-class="animate__fadeOutRight">
<component class="animate__animated animate__400ms" :is="Component" ref="main"
></component>
</transition>
</router-view>
</siderframe>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {elementScrollIntoView} from "seamless-scroll-polyfill";
import {useRoute, useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import {computed, onMounted} from "vue"; import {computed, onMounted, ref, provide} from "vue";
import api from "../api"; import api from "@/api";
import Siderframe from "@/components/siderframe.vue";
import {useMessage} from "naive-ui";
import {useStore} from "@/store";
let store = useStore()
let mgs = useMessage()
let route = useRoute() let route = useRoute()
let router = useRouter() let router = useRouter()
let uuid = computed(() => route.params.uuid) let uuid = computed(() => route.params.uuid)
let app = ref({})
provide('app', app)
provide('uuid', uuid)
let main = ref(null)
// @ts-ignore
let nav = computed(() => main.value ? main.value.nav : [])
let navRouter = ref(buildRouter())
function buildRouter() {
let navs = []
for (let n of router.getRoutes()) {
if (n.name && (n.name as string).startsWith('app')) {
if (n.meta.checkAuth) {
if (n.meta.checkAuth(store.state.user.auth)) {
navs.push(n)
}
} else {
navs.push(n)
}
}
}
return navs
}
function isEqualRoute(r: any) {
return r.name === route.name
}
onMounted(() => { onMounted(() => {
if (uuid.value === '') { if (uuid.value === '') {
router.push({name: '404', params: {path: route.path}}) router.push({name: '404', params: {path: route.path}})
return return
} }
api.app.get(uuid.value as string).Start(e => { api.app.get(uuid.value as string).Start(e => {
console.log(e) app.value = e
}, e => {
mgs.error('获取应用数据失败: ' + (e.err || e))
router.push({name: '404', params: {path: route.path}})
}) })
}) })
function goAnchor(element: any) {
// safari not support
// element.scrollIntoView({
// behavior: "smooth"
// })
elementScrollIntoView(element, {behavior: "smooth"});
}
</script> </script>
<style scoped> <style scoped>

@ -0,0 +1,27 @@
<template>
<div>
<h1 class="page-h1">首页</h1>
{{ uuid }}
{{ app }}
<div :ref="el => nav[6]=el" class="my-80">123123</div>
<div :ref="el => nav[k-1]=el" class="mb-64 text-center" v-for="(k) in [1,2,3,4,5,6]" :key="k">
{{ k }}
</div>
</div>
</template>
<script lang="ts" setup>
import {inject, ref} from "vue";
let uuid = inject('uuid')
let app = inject('app')
let nav = ref([])
defineExpose({
nav
})
</script>
<style scoped>
</style>

@ -0,0 +1,73 @@
<template>
<div>
<h1 class="page-h1">角色管理</h1>
<n-data-table
:bordered="false"
:columns="columns"
:data="roles"
/>
</div>
</template>
<script lang="ts" setup>
import {h, inject, onMounted, Ref, ref} from 'vue'
import api from '@/api'
import {NButton} from 'naive-ui'
let roles = ref([])
let uuid = inject<Ref>('uuid')
const columns = [
{
title: 'ID',
key: 'id',
width: 50,
},
{
title: '角色名',
key: 'name',
width: 100,
fixed: 'left',
},
{
title: '创建时间',
key: 'created_at',
fixed: 'left',
},
{
title: '绑定用户数',
key: 'user_count',
fixed: 'left',
},
{
title: '操作',
key: '',
render(row) {
return [
h(NButton, {
class: 'mr-1',
size: 'small',
onClick: () => console.log(row),
},
{default: () => '查看权限'}),
h(NButton, {
class: 'mr-1',
size: 'small',
onClick: () => console.log(row),
},
{default: () => '查看用户'},
),
]
},
},
]
onMounted(() => {
api.role(uuid.value).list().Start(e => {
roles.value = e
})
})
</script>
<style scoped>
</style>

@ -0,0 +1,74 @@
<template>
<div class="flex justify-center">
<div style="line-height: 48px" class="inline-block mt-16 grid grid-cols-5 w-1/3 text-center gap-4">
<div>应用名</div>
<div class="col-span-4">
<n-input v-model:value="data.name" @blur="update('name')"></n-input>
</div>
<div>logo</div>
<div class="col-span-4">
<n-upload
action="/api/upload"
@finish="handleFinish"
:show-file-list="false"
>
<n-avatar size="large" round :src="data.icon">
</n-avatar>
</n-upload>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {inject, watch, ref, onMounted} from "vue";
import api from "@/api";
import {useMessage} from "naive-ui";
let msg = useMessage()
let app: any = inject('app')
let data = ref({
name: '',
icon: ''
})
function handleFinish(e: any) {
if (e.event.target.response) {
let d = JSON.parse(e.event.target.response)
if (d.status === 1) {
data.value.icon = d.content
update('icon')
return
}
}
msg.error('上传失败')
data.value.icon = app.value.icon
}
function update(key: string) {
// @ts-ignore
let v = data.value[key]
if (v === app.value[key]) {
return
}
api.app.update(app.value.uuid, {[key]: v}).Start(e => {
msg.success('更新成功')
app.value[key] = v
}, e => {
data.value[key] = app.value[key]
})
}
function sync() {
data.value.name = app.value.name
data.value.icon = app.value.icon
}
watch(app, sync)
onMounted(sync)
</script>
<style scoped>
</style>

@ -0,0 +1,78 @@
<template>
<div>
<h1 class="page-h1">用户名单</h1>
<n-data-table
:bordered="false"
:columns="columns"
:data="users"
/>
</div>
</template>
<script lang="ts" setup>
import {inject, onMounted, ref, h} from "vue";
import api from "@/api";
import {NTag} from 'naive-ui'
let uuid: any = inject('uuid')
let users = ref([])
onMounted(() => {
api.app.user(uuid.value as string).list(0).Start(e => {
users.value = e
console.log(e)
})
})
const columns = [
{
title: 'ID',
key: 'user_id',
width: 50
},
{
title: '用户',
key: 'user.username',
width: 200,
fixed: 'left'
},
{
title: '加入时间',
key: 'user.created_at',
fixed: 'left'
},
{
title: 'Status',
key: 'status',
render(row) {
let t = statusTag(row.status)
return h(NTag,{
'type': t[1]
},
{
default: () => t[0]
}
)
}
}
]
function statusTag(s: string) {
switch (s) {
case 'ok':
return ['正常', 'success']
case "apply":
return ['申请中', 'info']
case 'deny':
return ['拒绝', '']
case 'disabled':
return ['禁用', 'warning']
}
return ['未知', '']
}
</script>
<style scoped>
</style>

@ -1,24 +1,57 @@
<template> <template>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center"> <div>
<div class="flex items-center justify-center" v-for="(item, k) in apps" :key="k"> <div v-if="ofApps.length > 0">
<AppCard :core="item"></AppCard> <h1 class="page-h1">已绑定应用</h1>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center">
<div v-for="(item, k) in ofApps" class="flex items-center justify-center" :key="k">
<AppCard :core="item"></AppCard>
</div>
<div class="flex items-center justify-center" v-for="(item) in '123456789'.split('')"
:key="item">
<AppCard :core2="{}"></AppCard>
</div>
</div>
</div> </div>
<div class="flex items-center justify-center" v-for="(item) in '1234567890'" :key="item"> <div v-if="apps.length > 0">
<AppCard :core="{}"></AppCard> <h1 class="page-h1">应用中心</h1>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center">
<div v-for="(item, k) in apps" class="flex items-center justify-center" :key="k">
<AppCard :core="item"></AppCard>
</div>
<div class="flex items-center justify-center" v-for="(item) in '123456'.split('')"
:key="item">
<AppCard :core2="{}"></AppCard>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {onMounted, ref} from "vue"; import {onMounted, ref} from "vue";
import api from "../api"; import api from "@/api";
import AppCard from '../components/app.vue' import AppCard from '@/components/app.vue'
import {useStore} from "@/store";
let store = useStore()
let apps = ref([]) let apps = ref([])
let ofApps = ref([])
function getApps() { function getApps() {
api.app.list().Start(e => { api.app.list().Start(e => {
apps.value = e apps.value = e
api.app.user('').list(store.state.user.id).Start(e => {
console.log(e)
ofApps.value = []
for (let i in e) {
let ai = apps.value.findIndex(a => a.id === e[i].app_id)
if (ai >= 0) {
apps.value[ai].user_status = e[i].status
ofApps.value.push(apps.value[ai])
apps.value.splice(ai, 1)
}
}
})
}) })
} }

@ -22,11 +22,11 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {computed, onMounted, ref} from "vue"; import {computed, onMounted, ref} from "vue";
import {Theme} from "../theme"; import {Theme} from "@/theme";
import {useMessage} from 'naive-ui' import {useMessage} from 'naive-ui'
import api from "../api" import api from "@/api"
import {useRoute, useRouter} from "vue-router"; import {useRoute, useRouter} from "vue-router";
import {store} from "../store"; import {store} from "@/store";
let msg = useMessage() let msg = useMessage()
const route = useRoute() const route = useRoute()

@ -1,10 +1,95 @@
<template> <template>
<div></div> <div class="flex items-center justify-center">
<div
:style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
class="px-10 pb-9 pt-28 rounded-xl w-96">
<n-form label-width="70px" label-align="left" :model="data" ref="form_ref" label-placement="left" :rules="rules">
<n-form-item required label="用户名" path="username">
<n-input @keydown.enter="divs[1].focus()" :ref="el => {if (el)divs[0]=el}"
v-model:value="data.username"></n-input>
</n-form-item>
<n-form-item required label="密码" path="password">
<n-input @keydown.enter="divs[2].focus()" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
type="password"></n-input>
</n-form-item>
<n-form-item required label="重复密码" path="pass">
<n-input @keydown.enter="register" :ref="el => {if (el) divs[2]=el}" v-model:value="data.pass"
type="password"></n-input>
</n-form-item>
<div class="flex justify-around mt-4">
<n-button @click="register"></n-button>
</div>
</n-form>
</div>
</div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {onMounted, ref} from "vue";
import {Theme} from "@/theme";
import {useMessage} from 'naive-ui'
import api from "@/api"
import {useRouter} from "vue-router";
let msg = useMessage()
const router = useRouter()
const divs = ref([])
let form_ref = ref(null)
let data = ref({
username: '',
password: '',
pass: ''
})
let rules = {
username: [
{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
},
trigger: ['input', 'blur']
}
],
password: [{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
},
trigger: ['input', 'blur']
}],
pass: [
{
required: true,
validator(r: any, v: any) {
return (v && v === data.value.password) || new Error('密码不正确')
},
trigger: ['input', 'blur']
}
]
}
function register() {
// @ts-ignore
form_ref.value.validate((e: any) => {
if (!e) {
api.user.register(data.value.username, data.value.password).Start((url: string) => {
msg.success('注册成功')
router.push({name: 'login'})
}, e => {
console.log(e)
msg.warning('注册失败:' + e)
})
}
})
}
onMounted(() => {
if (divs.value[0]) {
// @ts-ignore
divs.value[0].focus()
}
})
</script> </script>
<style scoped> <style scoped>
</style> </style>

@ -20,9 +20,9 @@
</n-form-item> </n-form-item>
<n-form-item label="头像"> <n-form-item label="头像">
<n-upload <n-upload
action="" action="/api/upload"
:headers="{'': ''}" @finish="handleFinish"
:data="{}" :show-file-list="false"
> >
<n-avatar size="large" round :src="user.icon"> <n-avatar size="large" round :src="user.icon">
</n-avatar> </n-avatar>
@ -51,9 +51,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import {ref, computed} from "vue"; import {ref, computed} from "vue";
import {IsDark} from "../theme"; import {IsDark} from "@/theme";
import {useStore} from "../store"; import {useStore} from "@/store";
import api from "../api"; import api from "@/api";
import {useMessage} from "naive-ui"; import {useMessage} from "naive-ui";
let msg = useMessage() let msg = useMessage()
@ -77,6 +77,19 @@ let emailOptions = computed(() => {
}) })
}) })
function handleFinish(e: any) {
if (e.event.target.response) {
let data = JSON.parse(e.event.target.response)
if (data.status === 1) {
user.value.icon = data.content
update('icon')
return
}
}
msg.error('上传失败')
user.value.icon = store.state.user.icon
}
function update(key: string) { function update(key: string) {
// @ts-ignore // @ts-ignore
let v = user.value[key] let v = user.value[key]

@ -4,10 +4,10 @@
</div> </div>
</template> </template>
<script setup lang='ts'> <script setup lang='ts'>
import WxLogin from '../components/WxLogin.vue' import WxLogin from '@/components/WxLogin.vue'
import {computed, onMounted} from "vue"; import {computed, onMounted} from "vue";
import {useRoute} from 'vue-router' import {useRoute} from 'vue-router'
import api from '../api' import api from '@/api'
let route = useRoute() let route = useRoute()

@ -4,12 +4,26 @@
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"strict": true, "strict": false,
"jsx": "preserve", "jsx": "preserve",
"sourceMap": true, "sourceMap": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["esnext", "dom"] "baseUrl": ".",
"paths": {
"@/*": [
"./src/*"
]
},
"lib": [
"esnext",
"dom"
]
}, },
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] "include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
} }

@ -1,12 +1,26 @@
import {defineConfig} from 'vite' import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import path from "path"
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"components": path.resolve(__dirname, "src/components"),
"styles": path.resolve(__dirname, "src/styles"),
"plugins": path.resolve(__dirname, "src/plugins"),
"views": path.resolve(__dirname, "src/views"),
"layouts": path.resolve(__dirname, "src/layouts"),
"utils": path.resolve(__dirname, "src/utils"),
"apis": path.resolve(__dirname, "src/apis"),
"dirs": path.resolve(__dirname, "src/directives"),
},
},
plugins: [vue()], plugins: [vue()],
server: { server: {
// host: '0.0.0.0', host: '0.0.0.0',
host: '127.0.0.1', // host: '127.0.0.1',
port: 8080, port: 8080,
proxy: { proxy: {
'/api': { '/api': {

@ -216,10 +216,10 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@veypi/one-icon@2.0.5": "@veypi/one-icon@2.0.6":
version "2.0.5" version "2.0.6"
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.5.tgz#1083be30fc3bbb89aaf19501b00110d3ae9b1c77" resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.6.tgz#158c692971848524cd59db1a61d88805d2e45646"
integrity sha512-THnQh1zbH+glwDBLjP7rtcF3UtTQnzsxnUQEtUjAjv9Eo6rVSa1u4RsFA8CMDpoVcQiTMhqgRalbfjovdr11wg== integrity sha512-ldfRE8vDSqZEFk+94wqieWP4s1Mz1EDG1VhXmckWI0cat2RT/Kk9hcICImkLhsOmhNRX7nwxSU4UbUiJVix/Jw==
dependencies: dependencies:
vue "^3.2.20" vue "^3.2.20"
@ -1841,6 +1841,11 @@ sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
seamless-scroll-polyfill@^2.1.5:
version "2.1.5"
resolved "https://registry.yarnpkg.com/seamless-scroll-polyfill/-/seamless-scroll-polyfill-2.1.5.tgz#ba3bc14ee14beb70a5287e2d3a0969b27cbf6112"
integrity sha512-aO9209XbxQecnX2Cb8x6u/KEvqqHmTMzgLuMtC2MSwfXcFKuI7jfVQ5b5n/wSLdfAec+r6rE96qVVKzZXcOx8A==
seemly@^0.3.1, seemly@^0.3.2: seemly@^0.3.1, seemly@^0.3.2:
version "0.3.2" version "0.3.2"
resolved "https://registry.yarnpkg.com/seemly/-/seemly-0.3.2.tgz#f45aacb2a0748bb9da766b5cf805d3b59ac9502c" resolved "https://registry.yarnpkg.com/seemly/-/seemly-0.3.2.tgz#f45aacb2a0748bb9da766b5cf805d3b59ac9502c"

@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

@ -0,0 +1 @@
src/libs/wwLogin.js

@ -0,0 +1,23 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/essential',
'@vue/standard',
'@vue/typescript/recommended'
],
parserOptions: {
ecmaVersion: 2020
},
rules: {
'object-curly-spacing': 0,
'space-before-function-paren': 0,
'@typescript-eslint/camelcase': 0,
'@typescript-eslint/no-empty-function': 0,
'@typescript-eslint/no-explicit-any': 0,
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
}
}

23
oaf2/.gitignore vendored

@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

@ -0,0 +1,24 @@
# oaf
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

@ -0,0 +1,49 @@
{
"name": "oaf",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@veypi/one-icon": "^1.0.1",
"axios": "^0.21.1",
"core-js": "^3.6.5",
"js-base64": "^3.6.0",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-m-message": "^3.1.0",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.2.0",
"vuetify": "^2.4.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-typescript": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/eslint-config-typescript": "^5.0.2",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"less": "^3.0.4",
"less-loader": "^5.0.0",
"sass": "^1.32.0",
"sass-loader": "^10.0.0",
"typescript": "~3.9.3",
"vue-cli-plugin-vuetify": "^2.2.2",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

File diff suppressed because one or more lines are too long

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico">-->
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

@ -0,0 +1,63 @@
<template>
<v-app @mousewheel.native.prevent="">
<v-app-bar
app
color="primary"
dark
>
<div class="d-flex align-center">
<one-icon style="color: aqua;font-size: 56px">glassdoor</one-icon>
<span class="font-italic font-weight-bold" style="font-size: 20px">统一认证</span>
</div>
<v-spacer></v-spacer>
</v-app-bar>
<v-main>
<router-view></router-view>
</v-main>
</v-app>
</template>
<script lang="ts">
import Vue from 'vue'
import util from '@/libs/util'
export default Vue.extend({
name: 'App',
components: {},
data: () => ({
//
}),
beforeCreate() {
util.title('统一认证')
this.$store.dispatch('fetchSelf')
}
})
</script>
<style lang="less">
@import './assets/common';
html,
body {
.full_size;
margin: 0;
padding: 0;
}
#app {
.full_size;
//.none_select;
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
</style>

@ -0,0 +1,65 @@
import axios from 'axios'
import store from '@/store'
function baseRequests(url: string, method: any = 'GET', query: any, data: any, success: any, fail?: Function) {
return axios({
url: url,
params: query,
data: data,
method: method,
headers: {
auth_token: localStorage.auth_token
}
})
.then((res: any) => {
if ('auth_token' in res.headers) {
localStorage.auth_token = res.headers.auth_token
}
if (method === 'HEAD') {
success(res.headers)
} else {
success(res.data)
}
})
.catch((e: any) => {
if (e.response && e.response.status === 401) {
console.log(e)
store.dispatch('handleLogout')
return
}
console.log(e)
if (e.response && e.response.status === 500) {
return
}
if (typeof fail === 'function') {
fail(e.response)
} else if (e.response && e.response.status === 400) {
console.log(400)
} else {
console.log(e.request)
}
})
}
const ajax = {
get(url: '', data = {}, success = {}, fail?: Function) {
return baseRequests(url, 'GET', data, {}, success, fail)
},
head(url: '', data = {}, success = {}, fail?: Function) {
return baseRequests(url, 'HEAD', data, {}, success, fail)
},
delete(url: '', data = {}, success = {}, fail?: Function) {
return baseRequests(url, 'DELETE', data, {}, success, fail)
},
post(url: '', data = {}, success = {}, fail?: Function) {
return baseRequests(url, 'POST', {}, data, success, fail)
},
put(url: '', data = {}, success = {}, fail?: Function) {
return baseRequests(url, 'PUT', {}, data, success, fail)
},
patch(url: '', data = {}, success = {}, fail?: Function) {
return baseRequests(url, 'PATCH', {}, data, success, fail)
}
}
export default ajax

@ -0,0 +1,216 @@
/*
* Copyright (C) 2019 light <light@light-laptop>
*
* Distributed under terms of the MIT license.
*/
import Vue from 'vue'
import {Base64} from 'js-base64'
import ajax from './ajax'
import store from '@/store'
export type SuccessFunction<T> = (e: any) => void;
export type FailedFunction<T> = (e: any) => void;
const Code = {
42011: '无操作权限',
22031: '资源不存在 或 您无权操作该资源'
}
class Interface {
private readonly method: Function
private readonly api: string
private readonly data: any
constructor(method: Function, api: string, data?: any) {
this.method = method
this.api = api
this.data = data
}
Start(success: SuccessFunction<any>, fail?: FailedFunction<any>) {
const newFail = function (data: any) {
if (data && data.code === 40001) {
// no login
store.dispatch('handleLogout')
return
}
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
if (data && data.code > 0 && Code[data.code]) {
}
if (fail) {
fail(data)
}
}
const newSuccess = function (data: any) {
if (Number(data.status) === 1) {
if (success) {
success(data.content)
}
} else {
newFail(data)
if (data.code === 41001) {
store.dispatch('handleLogout')
// bus.$emit('log_out')
}
}
}
this.method(this.api, this.data, newSuccess, newFail)
}
}
const message = {
count() {
return new Interface(ajax.get, '/api/message/', {
count: true,
status: 'UnRead'
})
},
get_content(id: number) {
return new Interface(ajax.get, '/api/message/' + Number(id))
},
list(status: string) {
return new Interface(ajax.get, '/api/message/', {status})
},
update(id: number, status: string) {
return new Interface(ajax.patch, '/api/message/' + Number(id), {status})
}
}
const role = {
local: '/api/role/',
get(id: number) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
},
update(id: number, props: any) {
return new Interface(ajax.patch, this.local + id, props)
},
create(props: any) {
return new Interface(ajax.post, this.local, props)
},
del(id: number) {
return new Interface(ajax.delete, this.local + id)
},
bind(id: number, aid: number) {
return new Interface(ajax.get, this.local + id + '/bind/' + aid)
},
unbind(id: number, aid: number) {
return new Interface(ajax.get, this.local + id + '/unbind/' + aid)
}
}
const app = {
local: '/api/app/',
self() {
return new Interface(ajax.get, this.local, {is_self: true})
},
get(id: string) {
return new Interface(ajax.get, this.local + id)
},
list() {
return new Interface(ajax.get, this.local)
}
}
const user = {
local: '/api/user/',
register(username: string, password: string, uuid: string, prop?: any) {
const data = Object.assign({
username: username,
uuid: uuid,
password: Base64.encode(password)
}, prop)
return new Interface(ajax.post, this.local, data)
},
login(username: string, password: string, uuid: string) {
return new Interface(ajax.head, this.local + username, {
uid_type: 'username',
uuid: uuid,
password: Base64.encode(password)
})
}
}
const api = {
role: role,
app: app,
user: user,
admin: {
auths() {
return new Interface(ajax.get, '/api/auth/')
},
user: {
create(props: any) {
const p = Object.assign({}, props)
p.password = Base64.encode(props.password)
return new Interface(ajax.post, '/api/user/', p)
},
update(user_id: number, props: any) {
return new Interface(ajax.patch, '/api/user/' + user_id, props)
},
enable(user_id: number) {
return new Interface(ajax.patch, '/api/user/' + user_id, {
status: 'ok'
})
},
disable(user_id: number) {
return new Interface(ajax.patch, '/api/user/' + user_id, {
status: 'disabled'
})
},
attach_role(user_id: number, props: any) {
return new Interface(ajax.post, '/api/user/' + user_id + '/role/', props)
},
detach_role(user_id: number, id: any) {
return new Interface(ajax.delete, '/api/user/' + user_id + '/role/' + id)
},
reset_pass(user_id: number, password: string) {
return new Interface(ajax.patch, '/api/user/' + user_id, {password})
}
}
},
auth: {
event() {
return {
local: '/api/user/event/',
list() {
return new Interface(ajax.get, this.local)
},
create(title: string, tag: string, start_date: any, end_date: any) {
return new Interface(ajax.post, this.local, {title, tag, start_date, end_date})
},
del(id: number) {
return new Interface(ajax.delete, this.local + id)
}
}
},
favorite(name: string, tag: string, ok: boolean) {
if (ok) {
return new Interface(ajax.post, '/api/user/favorite', {name, tag})
}
return new Interface(ajax.delete, '/api/user/favorite', {name, tag})
},
get(id: number) {
return new Interface(ajax.get, '/api/user/' + id)
},
search(username: string) {
return new Interface(ajax.get, '/api/user/', {
username
})
}
},
message: message
}
const Api = {
install(vue: typeof Vue): void {
vue.prototype.$api = api
}
}
export {Api}
export default api

@ -0,0 +1,25 @@
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
.full_size {
width: 100%;
height: 100%;
}
.none_select {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.none_border{
border: none;
}
.none_border_input {
.ivu-input {
.none_border;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 87.5 100"><defs><style>.cls-1{fill:#1697f6;}.cls-2{fill:#7bc6ff;}.cls-3{fill:#1867c0;}.cls-4{fill:#aeddff;}</style></defs><title>Artboard 46</title><polyline class="cls-1" points="43.75 0 23.31 0 43.75 48.32"/><polygon class="cls-2" points="43.75 62.5 43.75 100 0 14.58 22.92 14.58 43.75 62.5"/><polyline class="cls-3" points="43.75 0 64.19 0 43.75 48.32"/><polygon class="cls-4" points="64.58 14.58 87.5 14.58 43.75 100 43.75 62.5 64.58 14.58"/></svg>

After

Width:  |  Height:  |  Size: 539 B

@ -0,0 +1,44 @@
<template>
<div id="wx_reg"></div>
</template>
<script lang='ts'>
import {Component, Vue, Prop} from 'vue-property-decorator'
import '@/libs/wwLogin.js'
@Component({
components: {}
})
export default class WxLogin extends Vue {
goto(id: string, app: string, url: string, state?: number, href?: string) {
// eslint-disable-next-line
// @ts-ignore
window.WwLogin({
id: 'wx_reg',
appid: id,
agentid: app,
redirect_uri: encodeURIComponent(url),
state: state,
href: href
})
}
@Prop({default: ''})
aid = ''
@Prop({default: ''})
app = ''
@Prop({default: ''})
url = ''
mounted() {
this.goto(this.aid, this.app, this.url, new Date().getTime())
}
created() {
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,33 @@
<template>
<v-card class="core" elevation="4">
<v-system-bar color="info">
<one-icon>mail</one-icon>
<v-spacer></v-spacer>
<one-icon>user-group</one-icon>
<span>*{{ core.user_count }}</span>
</v-system-bar>
<div></div>
</v-card>
</template>
<script lang='ts'>
import {Component, Prop, Vue} from 'vue-property-decorator'
@Component({
components: {}
})
export default class AppCard extends Vue {
@Prop({default: {}})
core: any
mounted() {
console.log(this.core)
}
}
</script>
<style scoped>
.core {
width: 256px;
background: #2c3e50;
height: 128px;
}
</style>

@ -0,0 +1,20 @@
<template>
<div></div>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
@Component({
components: {}
})
export default class Demo extends Vue {
mounted() {
}
created() {
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,64 @@
function padLeftZero(str: string): string {
return ('00' + str).substr(str.length)
}
const util = {
title: function (title: string) {
window.document.title = title ? title + ' - oa' : 'veypi project'
},
getCookie(name: string) {
const reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)')
const arr = document.cookie.match(reg)
if (arr) {
return unescape(arr[2])
} else return null
},
delCookie(name: string) {
const exp = new Date()
exp.setTime(exp.getTime() - 1)
const cval = this.getCookie(name)
if (cval !== null) {
document.cookie = name + '=' + cval + ';expires=' + exp.toLocaleString()
}
},
setCookie(name: string, value: string, time: number) {
const exp = new Date()
exp.setTime(exp.getTime() + time)
document.cookie =
name + '=' + escape(value) + ';expires=' + exp.toLocaleString()
},
checkLogin() {
// return parseInt(this.getCookie('stat')) === 1
return Boolean(localStorage.auth_token)
},
formatDate(date: Date, fmt: string) {
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(
RegExp.$1,
(date.getFullYear() + '').substr(4 - RegExp.$1.length)
)
}
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
}
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
const str = o[k] + ''
fmt = fmt.replace(
RegExp.$1,
RegExp.$1.length === 1 ? str : padLeftZero(str)
)
}
}
return fmt
}
}
export default util

@ -0,0 +1,2 @@
!function(a,b,c){function d(c){var d=b.createElement("iframe"),e="https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid="+c.appid+"&agentid="+c.agentid+"&redirect_uri="+c.redirect_uri+"&state="+c.state+"&login_type=jssdk";e+=c.style?"&style="+c.style:"",e+=c.href?"&href="+c.href:"",d.src=e,d.frameBorder="0",d.allowTransparency="true",d.scrolling="no",d.width="300px",d.height="400px";var f=b.getElementById(c.id);f.innerHTML="",f.appendChild(d),d.onload=function(){d.contentWindow.postMessage&&a.addEventListener&&(a.addEventListener("message",function(b){
b.data&&b.origin.indexOf("work.weixin.qq.com")>-1&&(a.location.href=b.data)}),d.contentWindow.postMessage("ask_usePostMessage","*"))}}a.WwLogin=d}(window,document);

@ -0,0 +1,24 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
import {Api} from '@/api'
import OneIcon from '@veypi/one-icon'
import Message from 'vue-m-message'
import 'vue-m-message/dist/index.css'
Vue.use(Message) // will mount `Vue.prototype.$message`
// Vue.use(OneIcon, {href: 'https://at.alicdn.com/t/font_2872366_7aws02sx9bl.js'})
Vue.use(OneIcon, {href: './icon.js'})
Vue.use(Api)
Vue.config.productionTip = false
new Vue({
router,
store,
vuetify,
render: h => h(App)
}).$mount('#app')

@ -0,0 +1,24 @@
import Vue from 'vue'
import Vuetify from 'vuetify/lib/framework'
Vue.use(Vuetify)
const light = {
primary: '#2196f3',
secondary: '#00bcd4',
accent: '#3f51b5',
error: '#f44336',
warning: '#ff5722',
info: '#ff9800',
success: '#4caf50',
reset: '#684bff'
}
export default new Vuetify({
theme: {
dark: false,
themes: {
light: light
}
}
})

@ -0,0 +1,57 @@
import Vue from 'vue'
import VueRouter, {RouteConfig} from 'vue-router'
import Home from '../views/Home.vue'
import Demo from '@/views/demo.vue'
import Login from '@/views/login.vue'
import Register from '@/views/register.vue'
import NotFound from '@/views/404.vue'
Vue.use(VueRouter)
// 避免push到相同路径报错
// 获取原型对象上的push函数
const originalPush = VueRouter.prototype.push
// 修改原型对象中的push方法
VueRouter.prototype.push = function push(location: any) {
// eslint-disable-next-line
// @ts-ignore
return originalPush.call(this, location).catch(err => err)
}
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/app',
name: 'app',
component: Demo
},
{
path: '/login/:uuid?',
name: 'login',
component: Login
},
{
path: '/register/:uuid?',
name: 'register',
component: Register
},
{
path: '/wx',
name: 'wx',
component: () => import('../views/wx.vue')
},
{
path: '*',
name: '404',
component: NotFound
}
]
const router = new VueRouter({
routes
})
export default router

@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

@ -0,0 +1,5 @@
declare module '*.js'
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

@ -0,0 +1,4 @@
declare module 'vuetify/lib/framework' {
import Vuetify from 'vuetify'
export default Vuetify
}

@ -0,0 +1,30 @@
import Vue from 'vue'
import Vuex from 'vuex'
import api from '@/api'
import router from '@/router'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
oauuid: '',
user: null
},
mutations: {
setOA(state: any, data: any) {
state.oauuid = data.uuid
}
},
actions: {
fetchSelf({commit}) {
api.app.self().Start(d => {
commit('setOA', d)
})
},
handleLogout() {
localStorage.removeItem('auth_token')
router.push({name: 'login'})
}
},
modules: {}
})

@ -0,0 +1,14 @@
// 1. 确保在声明补充的类型之前导入 'vue'
import Vue from 'vue'
import api from '@/api'
export type PluginFunction<T> = (Vue: typeof Vue, options?: T) => void;
// 2. 定制一个文件,设置你想要补充的类型
// 在 types/vue.d.ts 里 Vue 有构造函数类型
declare module 'vue/types/vue' {
// 3. 声明为 Vue 补充的东西
interface Vue {
$api: typeof api;
}
}

@ -0,0 +1,24 @@
<style>
</style>
<template>
<div class='home d-flex justify-center align-center'>
<one-icon style="font-size: 100px">404</one-icon>
</div>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
import util from '@/libs/util'
@Component({
components: {}
})
export default class NotFound extends Vue {
mounted() {
}
created() {
util.title('404')
}
}
</script>

@ -0,0 +1,50 @@
<style>
.home {
height: 100%;
width: 100%;
}
</style>
<template>
<div class='full_size'>
<v-row no-gutters class="pa-8">
<v-col v-for="(item, key) in apps" :key="key" class="mx-4 my-2">
<AppCard :core="item"></AppCard>
</v-col>
</v-row>
</div>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
import util from '@/libs/util'
import AppCard from '@/components/app.vue'
@Component({
components: {
AppCard
}
})
export default class Home extends Vue {
apps = []
getApps() {
this.$api.app.list().Start(d => {
console.log(d)
this.apps = d
})
}
mounted() {
this.getApps()
}
created() {
}
beforeCreate() {
if (!util.checkLogin()) {
this.$router.push({name: 'login', query: this.$route.query, params: this.$route.params})
}
}
}
</script>

@ -0,0 +1,21 @@
<style>
</style>
<template>
<div class='home d-flex justify-center align-center'>
</div>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
@Component({
components: {}
})
export default class Demo extends Vue {
mounted() {
}
created() {
}
}
</script>

@ -0,0 +1,123 @@
<style>
</style>
<template>
<v-row align="center" class="fill-height" justify="center" style="background: #ebebeb">
<v-col cols="12" sm="8" md="6" lg="4" xl="3">
<v-card class="elevation-12 mx-5" style="opacity: 0.8">
<v-row justify="center">
<v-col cols="10">
<v-card class="elevation-1 mt-n12 primary theme--dark">
<v-card-text class="text-center">
<h1 class="display-2 font-weight-bold mb-2">Login</h1>
<v-tooltip left>
<template v-slot:activator="{ on }">
<v-btn icon large v-on="on">
<v-icon>mdi-cellphone</v-icon>
</v-btn>
</template>
<span style="font-family:'Noto Sans Armenian'">手机登录</span>
</v-tooltip>
<v-tooltip right>
<template v-slot:activator="{ on }">
<v-btn icon large v-on="on">
<v-icon>mdi-barcode</v-icon>
</v-btn>
</template>
<span>授权码登录</span>
</v-tooltip>
</v-card-text>
</v-card>
</v-col>
</v-row>
<v-card-text>
<v-form ref="form">
<v-text-field
v-model="formInline.user"
:counter="16"
:rules="ruleInline.user"
label="账号"
required
prepend-inner-icon="mdi-account-circle"
></v-text-field>
<v-text-field
v-model="formInline.password"
type="password"
:counter="16"
:rules="ruleInline.password"
label="密码"
prepend-inner-icon="mdi-lock"
@keyup.enter="handleSubmit"
required
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer/>
<v-btn type="primary" @click="handleSubmit"></v-btn>
<router-link :to="{name: 'register', query:$route.query, params: $route.params}"
style="text-decoration: none;">
<v-btn type="primary" style="margin-left:8px">注册</v-btn>
</router-link>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
import util from '@/libs/util'
@Component({
components: {}
})
export default class Login extends Vue {
formInline = {
user: '',
password: ''
}
ruleInline = {
user: [
(v: string) => !!v || 'required',
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16'
],
password: [
(v: string) => !!v || 'required',
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16'
]
}
get app_uuid() {
return this.$route.params.uuid || this.$store.state.oauuid
}
handleSubmit() {
// eslint-disable-next-line
// @ts-ignore
if (!this.$refs.form.validate()) {
return
}
this.$api.user.login(this.formInline.user, this.formInline.password, this.app_uuid).Start(
data => {
console.log(data)
if (util.checkLogin()) {
// this.$message.success('')
// EventBus.$emit('login', true)
this.$nextTick(() => {
if (this.$route.query.redirect) {
window.location.href = this.$route.query.redirect as string
}
this.$router.push({name: 'home'})
})
} else {
// this.$message.error('')
}
},
() => {
// this.$message.error('')
}
)
}
}
</script>

@ -0,0 +1,124 @@
<style>
</style>
<template>
<v-row class="fill-height" align="center" justify="center" style="background: #ebebeb">
<v-col cols="12" sm="8" md="6" lg="4" xl="3">
<v-card class="elevation-12 mx-5" style="opacity: 0.8">
<v-row justify="center">
<v-card class="elevation-1 mt-n7 primary" style="width: 80%">
<v-card-actions>
<v-row>
<v-icon
style="position: absolute;left: 10px;top:19px;z-index: 1"
@click="$router.back()"
size="36"
>mdi-arrow-left-circle
</v-icon>
<v-col cols="12" class="text-center">
<h1 class="display-2 ">注册</h1>
</v-col>
</v-row>
</v-card-actions>
</v-card>
</v-row>
<v-card-text class="text-center">
<v-form ref="form">
<v-text-field
type="text"
prepend-inner-icon="mdi-account-circle"
v-model="form.username"
label="账号"
:rules="ruleInline.user"
:counter="16"
>
</v-text-field>
<v-text-field
type="password"
v-model="form.passwd"
label="密码"
prepend-inner-icon="mdi-lock"
:rules="ruleInline.password"
:counter="16"
></v-text-field>
<v-text-field
type="password"
v-model="form.passwdCheck"
label="密码"
prepend-inner-icon="mdi-lock"
:rules="ruleInline.passwordCheck"
:counter="16"
@keyup.enter="handleSubmit"
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn type="primary" @click="handleSubmit"></v-btn>
<v-btn @click="handleReset()"></v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
@Component({
components: {}
})
export default class Register extends Vue {
form = {
passwd: '',
passwdCheck: '',
email: '',
username: ''
}
ruleInline = {
user: [
(v: string) => !!v || 'required',
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16'
],
password: [
(v: string) => !!v || 'required',
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16'
],
passwordCheck: [
(v: string) => !!v || 'required',
(v: string) => (v && v === this.form.passwd) || '密码不一致'
]
}
get app_uuid() {
return this.$route.params.uuid || this.$store.state.oauuid
}
handleSubmit() {
// eslint-disable-next-line
// @ts-ignore
if (!this.$refs.form.validate()) {
return
}
this.$api.user.register(this.form.username, this.form.passwd, this.app_uuid).Start(
() => {
this.$message.success('注册成功!')
this.$router.push({name: 'login', params: this.$route.params, query: this.$route.query})
},
(data) => {
if (data && data.code === '31011') {
this.$message.error('用户名重复')
} else {
this.$message.error('注册失败')
}
}
)
}
handleReset() {
this.form.username = ''
this.form.passwd = ''
this.form.passwdCheck = ''
}
}
</script>

@ -0,0 +1,66 @@
<template>
<div class='home d-flex justify-center align-center'>
<wx-login v-if="enable" :aid="aid" :app="agentID" :url="url"></wx-login>
<v-overlay :value="!enable">
<v-progress-circular
indeterminate
size="64"
></v-progress-circular>
</v-overlay>
</div>
</template>
<script lang='ts'>
import {Component, Vue} from 'vue-property-decorator'
import WxLogin from '@/components/WxLogin.vue'
@Component({
components: {
WxLogin
}
})
export default class Wx extends Vue {
aid = ''
agentID = ''
url = ''
get enable() {
return this.uuid && this.aid && this.agentID && this.url
}
get uuid() {
return this.$route.query.uuid
}
get code() {
return this.$route.query.code
}
get state() {
return this.$route.query.state
}
get msg() {
return this.$route.query.msg
}
mounted() {
if (this.msg) {
console.log(this.msg)
alert(this.msg)
}
}
created() {
if (this.uuid) {
this.$api.app.get(this.uuid as string).Start(e => {
this.url = e.wx.url + '/api/wx/login/' + this.uuid
this.aid = e.wx.corp_id
this.agentID = e.wx.agent_id
})
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,40 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

@ -0,0 +1,28 @@
module.exports = {
transpileDependencies: [
'vuetify'
],
configureWebpack: {
output: {
filename: '[name].[hash].js'
}
},
outputDir: '../sub/static',
devServer: {
host: '0.0.0.0',
port: 19520,
disableHostCheck: true,
proxy: {
'^/api': {
target: 'http://127.0.0.1:4001',
ws: true,
changeOrigin: true
},
'^/media': {
target: 'http://127.0.0.1:4001',
ws: true,
changeOrigin: true
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -35,7 +35,7 @@ func runAppList(c *cli.Context) error {
return err return err
} }
for _, a := range list { for _, a := range list {
log.Info().Msgf("%d: %s", a.ID, a.Name) log.Info().Msgf("%d: %s", a.UUID, a.Name)
} }
return nil return nil
} }

@ -5,8 +5,7 @@ import (
"OneAuth/libs/auth" "OneAuth/libs/auth"
"OneAuth/models" "OneAuth/models"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/veypi/utils/log" "github.com/veypi/utils"
"strconv"
) )
var Init = &cli.Command{ var Init = &cli.Command{
@ -18,32 +17,30 @@ func runInit(c *cli.Context) error {
return InitSystem() return InitSystem()
} }
// 初始化项目
var appid uint
func InitSystem() error { func InitSystem() error {
db() err := db()
if err != nil {
return err
}
self, err := selfApp() self, err := selfApp()
if err != nil { if err != nil {
return err return err
} }
appid = self.ID
err = role(self.InitRoleID == 0) err = role(self.InitRoleID == 0)
return err return err
} }
func db() { func db() error {
db := cfg.DB() db := cfg.DB()
log.HandlerErrs( err := utils.MultiErr(
db.SetupJoinTable(&models.User{}, "Roles", &models.UserRole{}), db.SetupJoinTable(&models.User{}, "Roles", &models.UserRole{}),
db.SetupJoinTable(&models.Role{}, "Users", &models.UserRole{}), db.SetupJoinTable(&models.Role{}, "Users", &models.UserRole{}),
db.SetupJoinTable(&models.User{}, "Apps", &models.AppUser{}), db.SetupJoinTable(&models.User{}, "Apps", &models.AppUser{}),
db.SetupJoinTable(&models.App{}, "Users", &models.AppUser{}), db.SetupJoinTable(&models.App{}, "Users", &models.AppUser{}),
db.AutoMigrate(&models.User{}, &models.Role{}, &models.Auth{}, &models.App{}), db.AutoMigrate(&models.User{}, &models.App{}, &models.Auth{}, &models.Role{}),
)
log.HandlerErrs(
db.AutoMigrate(&models.Wechat{}, &models.Resource{}), db.AutoMigrate(&models.Wechat{}, &models.Resource{}),
) )
return err
} }
func selfApp() (*models.App, error) { func selfApp() (*models.App, error) {
@ -64,7 +61,6 @@ func selfApp() (*models.App, error) {
EnableWx: false, EnableWx: false,
EnablePhone: false, EnablePhone: false,
EnableEmail: false, EnableEmail: false,
Wx: nil,
} }
return self, cfg.DB().Where("uuid = ?", self.UUID).FirstOrCreate(self).Error return self, cfg.DB().Where("uuid = ?", self.UUID).FirstOrCreate(self).Error
} }
@ -80,9 +76,8 @@ func role(reset_init_role bool) error {
} }
var err error var err error
adminRole := &models.Role{ adminRole := &models.Role{
AppID: appid, AppUUID: cfg.CFG.APPUUID,
Name: "admin", Name: "admin",
IsUnique: false,
} }
err = cfg.DB().Where(adminRole).FirstOrCreate(adminRole).Error err = cfg.DB().Where(adminRole).FirstOrCreate(adminRole).Error
if err != nil { if err != nil {
@ -90,10 +85,10 @@ func role(reset_init_role bool) error {
} }
for _, na := range n { for _, na := range n {
a := &models.Resource{ a := &models.Resource{
AppID: appid, AppUUID: cfg.CFG.APPUUID,
Name: na, Name: na,
Tag: "", Tag: "",
Des: "", Des: "",
} }
err = cfg.DB().Where(a).FirstOrCreate(a).Error err = cfg.DB().Where(a).FirstOrCreate(a).Error
if err != nil { if err != nil {
@ -106,20 +101,19 @@ func role(reset_init_role bool) error {
} }
} }
userRole := &models.Role{ userRole := &models.Role{
AppID: appid, AppUUID: cfg.CFG.APPUUID,
Name: "user", Name: "user",
IsUnique: false,
} }
err = cfg.DB().Where(userRole).FirstOrCreate(userRole).Error err = cfg.DB().Where(userRole).FirstOrCreate(userRole).Error
if err != nil { if err != nil {
return err return err
} }
err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(appid))) e1 := auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, "")
if err != nil { if err := utils.MultiErr(e1); err != nil {
return err return err
} }
if reset_init_role { if reset_init_role {
return cfg.DB().Model(&models.App{}).Where("id = ?", appid).Update("init_role_id", adminRole.ID).Error return cfg.DB().Model(&models.App{}).Where("uuid = ?", cfg.CFG.APPUUID).Update("init_role_id", adminRole.ID).Error
} }
return nil return nil
} }

@ -20,9 +20,9 @@ var Role = &cli.Command{
Name: "create", Name: "create",
Action: runRoleCreate, Action: runRoleCreate,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.UintFlag{ &cli.StringFlag{
Name: "id", Name: "uuid",
Usage: "app id", Usage: "app uuid",
Required: true, Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
@ -43,16 +43,16 @@ func runRoleList(c *cli.Context) error {
return err return err
} }
for _, r := range roles { for _, r := range roles {
log.Info().Msgf("%d %s@%d", r.ID, r.Name, r.AppID) log.Info().Msgf("%d %s@%d", r.ID, r.Name, r.AppUUID)
} }
return nil return nil
} }
func runRoleCreate(c *cli.Context) error { func runRoleCreate(c *cli.Context) error {
id := c.Uint("id") id := c.String("uuid")
name := c.String("name") name := c.String("name")
rl := &models.Role{} rl := &models.Role{}
rl.AppID = id rl.AppUUID = id
rl.Name = name rl.Name = name
err := cfg.DB().Where(rl).FirstOrCreate(rl).Error err := cfg.DB().Where(rl).FirstOrCreate(rl).Error
return err return err
@ -66,9 +66,9 @@ var Resource = &cli.Command{
Name: "list", Name: "list",
Action: runResourceList, Action: runResourceList,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.UintFlag{ &cli.StringFlag{
Name: "id", Name: "uuid",
Usage: "app id", Usage: "app uuid",
}, },
}, },
}, },
@ -76,9 +76,9 @@ var Resource = &cli.Command{
Name: "create", Name: "create",
Action: runResourceCreate, Action: runResourceCreate,
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.UintFlag{ &cli.StringFlag{
Name: "id", Name: "uuid",
Usage: "app id", Usage: "app uuid",
Required: true, Required: true,
}, },
&cli.StringFlag{ &cli.StringFlag{
@ -93,21 +93,21 @@ var Resource = &cli.Command{
func runResourceList(c *cli.Context) error { func runResourceList(c *cli.Context) error {
query := &models.Resource{} query := &models.Resource{}
query.AppID = c.Uint("id") query.AppUUID = c.String("uuid")
l := make([]*models.Resource, 0, 10) l := make([]*models.Resource, 0, 10)
err := cfg.DB().Where(query).Find(&l).Error err := cfg.DB().Where(query).Find(&l).Error
if err != nil { if err != nil {
return nil return nil
} }
for _, r := range l { for _, r := range l {
log.Info().Msgf("%d: %s@%d", r.ID, r.Name, r.AppID) log.Info().Msgf("%d: %s@%d", r.ID, r.Name, r.AppUUID)
} }
return nil return nil
} }
func runResourceCreate(c *cli.Context) error { func runResourceCreate(c *cli.Context) error {
query := &models.Resource{} query := &models.Resource{}
query.AppID = c.Uint("id") query.AppUUID = c.String("uuid")
query.Name = c.String("name") query.Name = c.String("name")
err := cfg.DB().Where(query).FirstOrCreate(query).Error err := cfg.DB().Where(query).FirstOrCreate(query).Error
return err return err

@ -1,17 +1,96 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite App</title> <title>Vite App</title>
<script type="module" crossorigin src="/static/index.dddecd43.js"></script> <script type="module" crossorigin src="/static/index.8f7b8116.js"></script>
<link rel="modulepreload" href="/static/vendor.ba3bd51d.js"> <link rel="modulepreload" href="/static/vendor.29407274.js">
<link rel="stylesheet" href="/static/vendor.3a295b6b.css"> <link rel="stylesheet" href="/static/vendor.7d59d594.css">
<link rel="stylesheet" href="/static/index.c49db26f.css"> <link rel="stylesheet" href="/static/index.22e5e80b.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<div id="loader-wrapper">
</body> <div class="lds-ripple">
<div></div>
<div></div>
</div>
<div class="load_title">Loading...
</div>
</div>
</body>
<style>
#loader-wrapper {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999999;
background: #999;
}
#loader-wrapper .load_title {
font-family: 'Open Sans';
color: #FFF;
font-size: 19px;
width: 100%;
text-align: center;
z-index: 9999999999999;
position: absolute;
top: 70%;
opacity: 1;
line-height: 30px;
}
#loader-wrapper .load_title span {
font-weight: normal;
font-style: italic;
font-size: 13px;
color: #FFF;
opacity: 0.5;
}
.lds-ripple {
display: inline-block;
position: relative;
left: calc(50% - 40px);
top: calc(70% - 100px);
width: 80px;
height: 80px;
}
.lds-ripple div {
position: absolute;
border: 4px solid #fff;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 72px;
height: 72px;
opacity: 0;
}
}
</style>
</html> </html>

@ -3,20 +3,19 @@ package sub
import ( import (
"OneAuth/api" "OneAuth/api"
"OneAuth/cfg" "OneAuth/cfg"
"embed"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"github.com/veypi/OneBD" "github.com/veypi/OneBD"
"github.com/veypi/utils/log" "github.com/veypi/utils/log"
) )
//go:embed static/static // go:embed static/static
var staticFiles embed.FS //var staticFiles embed.FS
//go:embed static/favicon.ico // go:embed static/favicon.ico
var icon []byte //var icon []byte
//go:embed static/index.html // go:embed static/index.html
var indexFile []byte //var indexFile []byte
var Web = &cli.Command{ var Web = &cli.Command{
Name: "web", Name: "web",
@ -41,9 +40,9 @@ func RunWeb(c *cli.Context) error {
// TODO media 文件需要检验权限 // TODO media 文件需要检验权限
app.Router().SubRouter("/media/").Static("/", cfg.CFG.MediaDir) app.Router().SubRouter("/media/").Static("/", cfg.CFG.MediaDir)
app.Router().EmbedDir("/static", staticFiles, "static/static/") //app.Router().EmbedDir("/static", staticFiles, "static/static/")
app.Router().EmbedFile("/favicon.ico", icon) //app.Router().EmbedFile("/favicon.ico", icon)
app.Router().EmbedFile("/*", indexFile) //app.Router().EmbedFile("/*", indexFile)
log.Info().Msg("\nRouting Table\n" + app.Router().String()) log.Info().Msg("\nRouting Table\n" + app.Router().String())
return app.Run() return app.Run()

Loading…
Cancel
Save