master
veypi 3 years ago
parent d7aea82ced
commit bc3f5e0b0c

@ -2,6 +2,7 @@
统一验证服务
## 用户验证思路
![](https://public.veypi.com/img/screenshot/20211012194238.png)

@ -3,22 +3,25 @@ package api
import (
"OneAuth/api/app"
"OneAuth/api/role"
"OneAuth/api/token"
"OneAuth/api/user"
"OneAuth/api/wx"
"OneAuth/libs/base"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/core"
)
func Router(r OneBD.Router) {
r.SetNotFoundFunc(func(m core.Meta) {
m.Write([]byte("{\"status\": 0}"))
base.JSONResponse(m, nil, nil)
})
r.SetInternalErrorFunc(func(m core.Meta) {
m.Write([]byte("{\"status\": 0}"))
base.JSONResponse(m, nil, nil)
})
user.Router(r.SubRouter("/user"))
wx.Router(r.SubRouter("wx"))
app.Router(r.SubRouter("app"))
token.Router(r.SubRouter("token"))
role.Router(r)
//message.Router(r.SubRouter("/message"))

@ -8,15 +8,17 @@ import (
"OneAuth/models"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc"
"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 {
h := &appHandler{}
h.Ignore(rfc.MethodGet)
h.Ignore(rfc.MethodGet, rfc.MethodPost)
return h
})
@ -31,7 +33,7 @@ func (h *appHandler) Get() (interface{}, error) {
isSelf := h.Meta().Query("is_self")
if isSelf != "" {
// 无权限可以获取本系统基本信息
h.query.ID = cfg.CFG.APPID
h.query.UUID = cfg.CFG.APPUUID
err := cfg.DB().Where(h.query).First(h.query).Error
return h.query, err
}
@ -64,3 +66,35 @@ func (h *appHandler) Get() (interface{}, error) {
err = cfg.DB().Find(&list).Error
return list, err
}
func (h *appHandler) Post() (interface{}, error) {
data := &struct {
Name string `json:"name"`
UUID string `json:"uuid"`
}{}
err := h.Meta().ReadJson(data)
if err != nil {
return nil, err
}
if data.Name == "" {
return nil, oerr.ApiArgsMissing.AttachStr("name")
}
_ = h.ParsePayload(h.Meta())
a := &models.App{
UUID: data.UUID,
Name: data.Name,
Key: utils.RandSeq(32),
Creator: h.Payload.ID,
}
a.Key = utils.RandSeq(32)
if data.UUID != "" {
err = cfg.DB().Where("uuid = ?", data.UUID).FirstOrCreate(a).Error
} else {
data.UUID = utils.RandSeq(16)
err = cfg.DB().Create(a).Error
}
if err != nil {
return nil, err
}
return a, nil
}

@ -0,0 +1,69 @@
package token
import (
"OneAuth/cfg"
"OneAuth/libs/app"
"OneAuth/libs/base"
"OneAuth/libs/oerr"
"OneAuth/libs/token"
"OneAuth/models"
"errors"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc"
"gorm.io/gorm"
)
func Router(r OneBD.Router) {
p := OneBD.NewHandlerPool(func() OneBD.Handler {
return &tokenHandler{}
})
r.Set("/:uuid", p, rfc.MethodGet)
}
type tokenHandler struct {
base.ApiHandler
}
func (h *tokenHandler) Get() (interface{}, error) {
uuid := h.Meta().Params("uuid")
if uuid == "" {
return nil, oerr.ApiArgsMissing.AttachStr("uuid")
}
a := &models.App{}
a.UUID = uuid
err := cfg.DB().Where("uuid = ?", uuid).First(a).Error
if err != nil {
return nil, err
}
au := &models.AppUser{
UserID: h.Payload.ID,
AppID: a.ID,
}
err = cfg.DB().Where(au).First(au).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
if a.EnableRegister {
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
return app.AddUser(cfg.DB(), au.AppID, au.UserID, a.InitRoleID, models.AUOK)
})
if err != nil {
return nil, err
}
au.Status = models.AUOK
} else {
return nil, oerr.AppNotJoin.AttachStr(a.Name)
}
}
return nil, oerr.DBErr.Attach(err)
}
if au.Status != models.AUOK {
return nil, oerr.NoAuth.AttachStr(string(au.Status))
}
u := &models.User{}
err = cfg.DB().Preload("Auths").Preload("Roles.Auths").Where("id = ?", h.Payload.ID).First(u).Error
if err != nil {
return nil, err
}
t, err := token.GetToken(u, a.ID, a.Key)
return t, err
}

@ -3,13 +3,11 @@ package user
import (
"OneAuth/cfg"
"OneAuth/libs/app"
"OneAuth/libs/auth"
"OneAuth/libs/base"
"OneAuth/libs/oerr"
"OneAuth/libs/token"
"OneAuth/models"
"errors"
//"OneAuth/ws"
"encoding/base64"
"fmt"
"github.com/veypi/OneBD"
@ -43,10 +41,13 @@ type handler struct {
// Get get user data
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().Preload("Scores").Preload("Roles.Auths").Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
err := cfg.DB().Where("username LIKE ? OR nickname LIKE ?", "%"+username+"%", "%"+username+"%").Find(&users).Error
if err != nil {
return nil, err
}
@ -56,14 +57,14 @@ func (h *handler) Get() (interface{}, error) {
if userID != 0 {
user := &models.User{}
user.ID = uint(userID)
return user, cfg.DB().Where(user).Preload("Scores").Preload("Roles.Auths").Preload("Favorites").First(user).Error
return user, cfg.DB().Where(user).First(user).Error
} else {
users := make([]models.User, 10)
skip, err := strconv.Atoi(h.Meta().Query("skip"))
if err != nil || skip < 0 {
skip = 0
}
if err := cfg.DB().Preload("Scores").Preload("Roles.Auths").Offset(skip).Find(&users).Error; err != nil {
if err := cfg.DB().Offset(skip).Find(&users).Error; err != nil {
return nil, err
}
return users, nil
@ -73,16 +74,15 @@ func (h *handler) Get() (interface{}, error) {
// Post register user
func (h *handler) Post() (interface{}, error) {
self := &models.App{}
self.ID = cfg.CFG.APPID
self.UUID = cfg.CFG.APPUUID
err := cfg.DB().Where(self).First(self).Error
if err != nil {
return nil, oerr.DBErr.Attach(err)
}
if !self.EnableRegister {
if !self.EnableRegister && !h.Payload.GetAuth(auth.User, "").CanCreate() {
return nil, oerr.NoAuth.AttachStr("register disabled")
}
var userdata = struct {
UUID string `json:"uuid"`
Username string `json:"username"`
Password string `json:"password"`
Nickname string `json:"nickname"`
@ -120,27 +120,10 @@ func (h *handler) Post() (interface{}, error) {
if err := tx.Create(&h.User).Error; err != nil {
return oerr.ResourceDuplicated
}
err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID)
err := app.AddUser(tx, self.ID, h.User.ID, self.InitRoleID, models.AUOK)
if err != nil {
return err
}
if userdata.UUID != self.UUID && userdata.UUID != "" {
target := &models.App{}
target.UUID = userdata.UUID
err = tx.Where(target).First(target).Error
if err != nil {
return err
}
if target.EnableRegister {
err := app.AddUser(tx, target.ID, h.User.ID, target.InitRoleID)
if err != nil {
return err
}
} else {
// TODO
}
return err
}
return nil
})
if err != nil {
@ -169,11 +152,10 @@ func (h *handler) Patch() (interface{}, error) {
} else {
target.ID = uint(tempID)
}
tx := cfg.DB().Begin()
if err := cfg.DB().Where(&target).First(&target).Error; err != nil {
return nil, err
}
if target.ID != h.Payload.ID {
if target.ID != h.Payload.ID && h.Payload.GetAuth(auth.User, strconv.Itoa(int(target.ID))).CanUpdate() {
return nil, oerr.NoAuth
}
if len(opts.Password) >= 6 {
@ -197,11 +179,9 @@ func (h *handler) Patch() (interface{}, error) {
if opts.Status != "" {
target.Status = opts.Status
}
if err := tx.Updates(&target).Error; err != nil {
tx.Rollback()
if err := cfg.DB().Updates(&target).Error; err != nil {
return nil, err
}
tx.Commit()
return nil, nil
}
@ -222,10 +202,6 @@ func (h *handler) Head() (interface{}, error) {
if len(uid) == 0 || len(password) == 0 {
return nil, oerr.ApiArgsError
}
appUUID := h.Meta().Query("uuid")
if appUUID == "" {
return nil, oerr.ApiArgsMissing.AttachStr("uuid")
}
h.User = new(models.User)
uidType := h.Meta().Query("uid_type")
switch uidType {
@ -239,7 +215,7 @@ func (h *handler) Head() (interface{}, error) {
h.User.Username = uid
}
target := &models.App{}
target.UUID = appUUID
target.UUID = cfg.CFG.APPUUID
err = cfg.DB().Where(target).Find(target).Error
if err != nil {
return nil, oerr.DBErr.Attach(err)
@ -261,17 +237,12 @@ func (h *handler) Head() (interface{}, error) {
au.AppID = target.ID
err = cfg.DB().Where(au).First(au).Error
appID := target.ID
h.Meta().SetHeader("content", target.UserRefreshUrl)
if err != nil {
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, err
}
appID = cfg.CFG.APPID
h.Meta().SetHeader("content", "/app/"+target.UUID)
} else if au.Disabled {
return nil, err
} else if au.Status != models.AUOK {
return nil, oerr.DisableLogin
}
tokenStr, err := token.GetToken(h.User, appID)
tokenStr, err := token.GetToken(h.User, appID, cfg.CFG.APPKey)
if err != nil {
log.HandlerErrs(err)
return nil, oerr.Unknown.Attach(err)

@ -15,11 +15,11 @@ var CFG = &struct {
Host string
LoggerPath string
LoggerLevel string
APPID uint
APPUUID string
APPKey string
TimeFormat string
Debug bool
EXEDir string
MediaDir string
DB struct {
Type string
Addr string
@ -28,14 +28,15 @@ var CFG = &struct {
DB string
}
}{
APPID: 1,
APPUUID: "jU5Jo5hM",
APPKey: "cB43wF94MLTksyBK",
AdminUser: "admin",
Host: "0.0.0.0:4001",
LoggerPath: "",
LoggerLevel: "debug",
TimeFormat: "2006/01/02 15:04:05",
Debug: true,
EXEDir: "./",
MediaDir: "/Users/light/test/media/",
DB: struct {
Type string
Addr string

@ -8,7 +8,10 @@ import (
"gorm.io/gorm"
)
func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error {
func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint, status models.AUStatus) error {
if appID == 0 || userID == 0 {
return oerr.FuncArgsError
}
au := &models.AppUser{}
au.AppID = appID
au.UserID = userID
@ -17,19 +20,25 @@ func AddUser(tx *gorm.DB, appID uint, userID uint, roleID uint) error {
return oerr.ResourceDuplicated
}
if errors.Is(err, gorm.ErrRecordNotFound) {
au.Status = status
err = tx.Create(au).Error
if err != nil {
return err
}
err = auth.BindUserRole(tx, userID, roleID)
if err != nil {
return err
if roleID > 0 {
err = auth.BindUserRole(tx, userID, roleID)
if err != nil {
return err
}
}
return tx.Model(&models.App{}).Where("id = ?", appID).Update("user_count", gorm.Expr("user_count + ?", 1)).Error
}
return err
}
func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
if appID == 0 || userID == 0 {
return oerr.FuncArgsError
}
au := &models.AppUser{}
au.AppID = appID
au.UserID = userID
@ -37,12 +46,18 @@ func EnableUser(tx *gorm.DB, appID uint, userID uint) error {
if err != nil {
return err
}
return tx.Where(au).Update("disabled", false).Error
if au.Status != models.AUOK {
return tx.Where(au).Update("status", models.AUOK).Error
}
return nil
}
func DisableUser(tx *gorm.DB, appID uint, userID uint) error {
if appID == 0 || userID == 0 {
return oerr.FuncArgsError
}
au := &models.AppUser{}
au.AppID = appID
au.UserID = userID
return tx.Where(au).Update("disabled", true).Error
return tx.Where(au).Update("status", models.AUDisable).Error
}

@ -3,10 +3,12 @@ package base
import (
"OneAuth/libs/oerr"
"OneAuth/libs/tools"
"errors"
"github.com/json-iterator/go"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/rfc"
"github.com/veypi/utils/log"
"gorm.io/gorm"
"strconv"
"sync"
"time"
@ -14,6 +16,41 @@ import (
var json = jsoniter.ConfigFastest
func JSONResponse(m OneBD.Meta, data interface{}, err error) {
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = oerr.ResourceNotExist
}
}
if m.Method() == rfc.MethodHead {
if err != nil {
m.SetHeader("status", "0")
m.SetHeader("code", strconv.Itoa(int(oerr.OfType(err.Error()))))
m.SetHeader("err", err.Error())
} else {
m.SetHeader("status", "1")
}
return
}
res := map[string]interface{}{
"status": 1,
}
if err != nil {
res["status"] = 0
res["code"] = oerr.OfType(err.Error())
res["err"] = err.Error()
} else {
res["status"] = 1
res["content"] = data
}
p, err := json.Marshal(res)
if err != nil {
log.Warn().Err(err).Msg("encode json data error")
return
}
_, _ = m.Write(p)
}
type ApiHandler struct {
OneBD.BaseHandler
UserHandler
@ -24,29 +61,12 @@ func (h *ApiHandler) Init(m OneBD.Meta) error {
}
func (h *ApiHandler) OnResponse(data interface{}) {
if h.Meta().Method() == rfc.MethodHead {
h.Meta().SetHeader("status", "1")
return
}
p, err := json.Marshal(map[string]interface{}{"status": 1, "content": data})
if err != nil {
log.Warn().Err(err).Msg("encode json data error")
return
}
h.Meta().Write(p)
JSONResponse(h.Meta(), data, nil)
}
func (h *ApiHandler) OnError(err error) {
log.WithNoCaller.Warn().Err(err).Msg(h.Meta().RequestPath())
msg := err.Error()
if h.Meta().Method() == rfc.MethodHead {
h.Meta().SetHeader("status", "0")
h.Meta().SetHeader("code", strconv.Itoa(int(oerr.OfType(msg))))
h.Meta().SetHeader("err", msg)
} else {
p, _ := json.Marshal(map[string]interface{}{"status": 0, "code": oerr.OfType(msg), "err": msg})
h.Meta().Write(p)
}
JSONResponse(h.Meta(), nil, err)
}
var ioNumLimit = make(map[string]time.Time)

@ -1,6 +1,7 @@
package base
import (
"OneAuth/cfg"
"OneAuth/libs/oerr"
"OneAuth/libs/token"
"OneAuth/models"
@ -26,7 +27,7 @@ func (a *UserHandler) ParsePayload(m OneBD.Meta) error {
if tokenStr == "" {
return oerr.NotLogin
}
ok, err := token.ParseToken(tokenStr, a.Payload)
ok, err := token.ParseToken(tokenStr, a.Payload, cfg.CFG.APPKey)
if ok {
return nil
}

@ -1,7 +1,6 @@
package oerr
import (
"errors"
"gorm.io/gorm"
"strconv"
)
@ -42,11 +41,13 @@ type Code uint
- 9 :
*/
// Unknown error
const (
Unknown Code = 0
)
const (
// 2 数据库错误
// DBErr 2 数据库错误
// -1 系统错误
// -2 数据读写错误
DBErr Code = 20001
@ -56,10 +57,13 @@ const (
)
const (
// 3
// LogicErr 3 系统内逻辑错误
LogicErr Code = 30000
AppNotJoin Code = 30001
)
const (
// NotLogin
// 4 权限类型错误
// 1: 登录权限
// 2: 资源操作权限
@ -130,6 +134,8 @@ var codeMap = map[Code]string{
NoAuth: "no auth to access",
AccessErr: "access error",
AccessTooFast: "access too fast",
LogicErr: "logic error",
AppNotJoin: "not join in app",
}
func (c Code) Error() string {
@ -144,7 +150,7 @@ func (c Code) String() string {
return codeMap[Unknown]
}
// 附加错误详细原因
// Attach 附加错误详细原因
func (c Code) Attach(errs ...error) (e error) {
e = c
for _, err := range errs {
@ -195,16 +201,3 @@ func (w *wrapErr) Error() string {
func (w *wrapErr) UnWrap() error {
return w.err
}
func CheckMultiErr(errs ...error) error {
msg := ""
for _, e := range errs {
if e != nil {
msg += e.Error() + "\n"
}
}
if msg != "" {
return errors.New(msg)
}
return nil
}

@ -1,7 +1,6 @@
package token
import (
"OneAuth/libs/key"
"OneAuth/models"
"github.com/veypi/utils/jwt"
)
@ -16,9 +15,8 @@ type simpleAuth struct {
// TODO:: roles 是否会造成token过大 ?
type PayLoad struct {
jwt.Payload
ID uint `json:"id"`
AppID uint `json:"app_id"`
Auth map[uint]*simpleAuth `json:"auth"`
ID uint `json:"id"`
Auth map[uint]*simpleAuth `json:"auth"`
}
// GetAuth resource_uuid 缺省或仅第一个有效 权限会被更高权限覆盖
@ -49,11 +47,10 @@ func (p *PayLoad) GetAuth(ResourceID string, ResourceUUID ...string) models.Auth
return res
}
func GetToken(u *models.User, appID uint) (string, error) {
func GetToken(u *models.User, appID uint, key string) (string, error) {
payload := &PayLoad{
ID: u.ID,
AppID: appID,
Auth: map[uint]*simpleAuth{},
ID: u.ID,
Auth: map[uint]*simpleAuth{},
}
for _, a := range u.GetAuths() {
if appID == a.AppID {
@ -64,9 +61,9 @@ func GetToken(u *models.User, appID uint) (string, error) {
}
}
}
return jwt.GetToken(payload, []byte(key.User(payload.ID, payload.AppID)))
return jwt.GetToken(payload, []byte(key))
}
func ParseToken(token string, payload *PayLoad) (bool, error) {
return jwt.ParseToken(token, payload, []byte(key.User(payload.ID, payload.AppID)))
func ParseToken(token string, payload *PayLoad, key string) (bool, error) {
return jwt.ParseToken(token, payload, []byte(key))
}

@ -7,7 +7,6 @@ import (
"github.com/veypi/utils/cmd"
"github.com/veypi/utils/log"
"os"
"path/filepath"
)
const Version = "v0.1.0"
@ -35,21 +34,6 @@ func main() {
Value: cfg.CFG.LoggerPath,
Destination: &cfg.CFG.LoggerPath,
},
&cli.UintFlag{
Name: "id",
Value: cfg.CFG.APPID,
Destination: &cfg.CFG.APPID,
},
&cli.StringFlag{
Name: "key",
Value: cfg.CFG.APPKey,
Destination: &cfg.CFG.APPKey,
},
&cli.StringFlag{
Name: "exe_dir",
Value: cfg.CFG.EXEDir,
Destination: &cfg.CFG.EXEDir,
},
&cli.StringFlag{
Name: "host",
Value: cfg.CFG.Host,
@ -72,11 +56,6 @@ func main() {
srv.SetStopFunc(func() {
})
app.Before = func(c *cli.Context) error {
var err error
cfg.CFG.EXEDir, err = filepath.Abs(cfg.CFG.EXEDir)
if err != nil {
return err
}
if cfg.CFG.Debug {
cfg.CFG.LoggerLevel = "debug"
}

@ -42,12 +42,22 @@ type App struct {
Wx *Wechat `json:"wx" gorm:"foreignkey:AppID;references:ID"`
}
type AUStatus string
const (
AUOK AUStatus = "ok"
AUDisable AUStatus = "disabled"
AUApply AUStatus = "apply"
AUDeny AUStatus = "deny"
)
type AppUser struct {
BaseModel
AppID uint `json:"app_id"`
UserID uint `json:"user_id"`
Disabled bool `json:"disabled"`
Status string `json:"status"`
AppID uint `json:"app_id"`
APP *App `json:"app"`
UserID uint `json:"user_id"`
User *User `json:"user"`
Status AUStatus `json:"status"`
}
type Wechat struct {

@ -7,7 +7,8 @@
"serve": "vite preview"
},
"dependencies": {
"@veypi/one-icon": "2",
"@veypi/one-icon": "2.0.5",
"animate.css": "^4.1.1",
"axios": "^0.24.0",
"js-base64": "^3.7.2",
"vue": "^3.2.16",
@ -18,6 +19,7 @@
"@tailwindcss/postcss7-compat": "^2.1.0",
"@vitejs/plugin-vue": "^1.9.3",
"autoprefixer": "^9.8.8",
"less": "^4.1.2",
"naive-ui": "^2.19.11",
"postcss": "^7.0.39",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",

File diff suppressed because one or more lines are too long

@ -1,59 +1,99 @@
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
import { onBeforeMount, ref} from 'vue'
import util from './libs/util'
import {store} from "./store";
import {useOsTheme} from 'naive-ui'
const osThemeRef = useOsTheme()
onBeforeMount(() => {
util.title("统一认证")
store.dispatch('fetchSelf')
store.commit('setTheme', osThemeRef.value)
})
</script>
<template>
<n-config-provider :theme="store.getters.GetTheme" class="h-full w-full">
<n-layout class="h-full w-full font-sans select-none">
<n-layout has-sider style="height: calc(100% - 24px)">
<n-layout-sider
class="h-full"
collapse-mode="transform"
:collapsed-width="0"
:width="120"
show-trigger="bar"
content-style="padding: 24px;"
bordered
default-collapsed
>
-
</n-layout-sider>
<n-layout>
<n-layout-header bordered style="height: 64px;line-height: 64px;">
{{ osThemeRef }}
<one-icon @click="store.dispatch('changeTheme')" class="float-right" style="font-size: 36px; margin: 14px">
{{ store.getters.IsDark ? 'Daytimemode' : 'nightmode-fill' }}
<n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN"
:theme="Theme">
<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>
</n-layout-header>
<n-layout style="height: calc(100% - 64px)">
<router-view class="h-full w-full"></router-view>
</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 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 style="height: 24px;line-height: 24px" class="flex justify-around px-3 text-gray-500 text-xs">
<span class="hover:text-black cursor-pointer">关于OA</span>
<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">
<span class="hover:text-black cursor-pointer" @click="goto('https://veypi.com')">
©2021 veypi
</span>
</n-layout-footer>
</n-layout>
</n-config-provider>
</template>
<script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs
import {onBeforeMount, ref} from 'vue'
import util from './libs/util'
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()
onBeforeMount(() => {
util.title("统一认证")
store.dispatch('fetchSelf')
store.dispatch('user/fetchUserData')
})
let goto = (url: any) => {
window.open(url, "_blank")
}
let collapsed = ref(true)
</script>
<style lang="less">
.animate__400ms {
--animate-duration: 400ms;
}
<style>
html,
body {
width: 100%;
@ -73,6 +113,12 @@ body {
height: 100%;
}
.header-icon {
display: inline-block;
font-size: 24px;
margin: 20px 10px 20px 10px;
}
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
@ -82,6 +128,9 @@ body {
height: 100%;
}
.main {
}
::-webkit-scrollbar {
display: none; /* Chrome Safari */
}

@ -13,19 +13,19 @@ function baseRequests(url: string, method: any = 'GET', query: any, data: any, s
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)
}
})
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')
store.commit('user/logout')
return
}
console.log(e)

@ -10,7 +10,6 @@ import {store} from '../store'
import {Base64} from 'js-base64'
export type SuccessFunction<T> = (e: any) => void;
export type FailedFunction<T> = (e: any) => void;
@ -34,15 +33,15 @@ class Interface {
const newFail = function (data: any) {
if (data && data.code === 40001) {
// no login
store.dispatch('handleLogout')
store.commit('user/logout')
return
}
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
if (data && data.code > 0 && Code[data.code]) {
if (data && data.code && Code[data.code]) {
}
if (fail) {
fail(data)
fail(data.err)
}
}
@ -54,7 +53,7 @@ class Interface {
} else {
newFail(data)
if (data.code === 41001) {
store.dispatch('handleLogout')
store.commit('user/logout')
// bus.$emit('log_out')
}
}
@ -92,6 +91,15 @@ const user = {
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)
}
}

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
<template>
<div class="core rounded-2xl p-3">
<div class="grid gap-4 grid-cols-5">
<div class="col-span-2">
<n-avatar @click="$router.push({name: 'app', params: {uuid: core.uuid}})" round :size="80" :src="core.icon">
{{ core.icon ? '' : core.name }}
</n-avatar>
</div>
<div class="col-span-3 grid grid-cols-1 items-center text-left">
<div class="h-10 flex items-center text-2xl italic font-bold">{{ core.name }}</div>
<div class="select-all">{{ core.uuid }}</div>
</div>
</div>
<textarea disabled style="background: none;border: none" class="focus:outline-none w-full">{{core.des}}</textarea>
</div>
</template>
<script setup lang='ts'>
import {defineProps} from "vue";
let props = defineProps<{
core: any
}>()
</script>
<style scoped>
.core {
width: 256px;
background: #2c3e50;
}
</style>

@ -0,0 +1,61 @@
<template>
<base_frame style="line-height:40px" v-model="shown" :isDark="IsDark">
<div class="flex">
<n-avatar :src="$store.state.user.icon" round></n-avatar>
</div>
<template v-slot:main>
<div style="height: 100%">
<div style="height: calc(100% - 50px)">
<div class="w-full px-3">
<div class="h-16 flex justify-between items-center">
<span style="color: #777">我的账户</span>
<span @click="$router.push({name: 'user_setting'});shown=false" class="cursor-pointer"
style="color:#f36828">账户中心</span>
</div>
<div class="grid grid-cols-4 gap-4 h-20">
<div class="flex items-center justify-center">
<n-avatar size="50" :src="$store.state.user.icon" round></n-avatar>
</div>
<div class="col-span-2 text-xs grid grid-cols-1 items-center" style="">
<span>昵称: &ensp;&ensp; {{ $store.state.user.nickname }}</span>
<span>账户: &ensp;&ensp; {{ $store.state.user.username }}</span>
<span>邮箱: &ensp;&ensp; {{ $store.state.user.email }}</span>
</div>
<div class="">123</div>
</div>
<hr class="mt-10" style="border:none;border-top:1px solid #777;">
</div>
</div>
<hr style="border:none;border-top:2px solid #777;">
<div style="height: 48px">
<div @click="$store.commit('user/logout')"
class="w-full h-full flex justify-center items-center cursor-pointer transition duration-500 ease-in-out transform hover:scale-125">
<one-icon :color="IsDark?'#eee': '#333'" class="inline-block" style="font-size: 24px;">
logout
</one-icon>
<div>
退出登录
</div>
</div>
</div>
</div>
</template>
</base_frame>
</template>
<script lang="ts" setup>
import base_frame from './frame.vue'
import {IsDark} from '../../theme'
import {ref} from "vue";
let shown = ref(false)
function asd(e) {
console.log([e, shown.value])
}
</script>
<style scoped>
</style>

@ -0,0 +1,82 @@
<template>
<div>
<div @click="setValue(true)">
<slot>
</slot>
</div>
<div @click.self="setValue(false)" class="core" style="height: 100vh;width: 100vw;" v-if="props.modelValue">
<div style="height: 100%; width: 300px" class="core-right">
<transition appear enter-active-class="animate__slideInRight">
<div class="right-title animate__animated animate__faster">
<slot name="title"></slot>
<div class="flex items-center float-right h-full px-1">
<one-icon @click="setValue(false)" color="#fff" style="font-size: 24px">close</one-icon>
</div>
</div>
</transition>
<div class="right-main">
<transition appear enter-active-class="animate__slideInDown">
<div class="right-main-core animate__animated animate__faster"
:style="{'background': props.isDark ? '#222': '#eee'}">
<slot name="main"></slot>
</div>
</transition>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits, watch} from "vue";
let emits = defineEmits<{
(e: 'update:modelValue', v: boolean): void
}>()
let props = defineProps<{
isDark: boolean,
modelValue: boolean
}>()
function setValue(b: boolean) {
emits('update:modelValue', b)
}
</script>
<style scoped>
.core {
position: fixed;
left: 0;
top: 0;
background: rgba(0, 0, 0, 0.4);
z-index: 100;
}
.core-right {
position: absolute;
right: 0;
top: 0;
}
.right-main {
width: 100%;
height: calc(100% - 50px);
overflow: hidden;
}
.right-main-core {
height: 100%;
width: 100%;
-webkit-animation-delay: 0.4s;
animation-delay: 0.4s;
--animate-duration: 400ms;
}
.right-title {
width: 100%;
height: 50px;
line-height: 50px;
background: linear-gradient(90deg, #f74d22, #fa9243);
}
</style>

@ -0,0 +1,3 @@
import avatar from './avatar.vue'
export default avatar

@ -0,0 +1,10 @@
<template>
<div></div>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>

@ -0,0 +1,71 @@
<template>
<div @click="handleFullscreen">
<one-icon>{{ props.modelValue ? 'fullscreen-exit' : 'fullscreen' }}</one-icon>
</div>
</template>
<script lang="ts" setup>
import {defineEmits, onMounted, defineProps} from "vue";
let emit = defineEmits<{
(e: 'update:modelValue', v: boolean): void
}>()
let props = defineProps<{
modelValue: boolean
}>()
function handleFullscreen() {
let main = document.body
if (props.modelValue) {
if (document.exitFullscreen) {
document.exitFullscreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
} else if (document.webkitCancelFullScreen) {
document.webkitCancelFullScreen()
} else if (document.msExitFullscreen) {
document.msExitFullscreen()
}
} else {
if (main.requestFullscreen) {
main.requestFullscreen()
} else if (main.mozRequestFullScreen) {
main.mozRequestFullScreen()
} else if (main.webkitRequestFullScreen) {
main.webkitRequestFullScreen()
} else if (main.msRequestFullscreen) {
main.msRequestFullscreen()
}
}
}
onMounted(() => {
let isFullscreen =
document.fullscreenElement ||
document.mozFullScreenElement ||
document.webkitFullscreenElement ||
document.fullScreen ||
document.mozFullScreen ||
document.webkitIsFullScreen
isFullscreen = !!isFullscreen
document.addEventListener('fullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
document.addEventListener('mozfullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
document.addEventListener('webkitfullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
document.addEventListener('msfullscreenchange', () => {
emit('update:modelValue', !props.modelValue)
})
emit('update:modelValue', isFullscreen)
})
</script>
<style>
</style>

@ -0,0 +1,2 @@
import fullscreen from './fullscreen.vue'
export default fullscreen

@ -27,6 +27,9 @@ const util = {
document.cookie =
name + '=' + escape(value) + ';expires=' + exp.toLocaleString()
},
getToken() {
return localStorage.auth_token
},
checkLogin() {
// return parseInt(this.getCookie('stat')) === 1
return Boolean(localStorage.auth_token)

@ -6,12 +6,14 @@ import OneIcon from '@veypi/one-icon'
import naive from 'naive-ui'
import './index.css'
import {Api} from './api'
import './assets/icon.js'
import 'animate.css'
const app = createApp(App)
app.use(Api)
app.use(naive)
app.use(OneIcon, {href: './icon.js'})
app.use(OneIcon)
app.use(router)
app.use(store, key)
app.mount('#app')

@ -22,12 +22,25 @@ const router = createRouter({
component: () => import('../views/home.vue')
},
{
path: '/app',
path: '/app/:uuid?',
name: 'app',
meta: {
requiresAuth: true,
},
component: () => import('../views/demo.vue')
component: () => import('../views/app.vue')
},
{
path: '/user/setting',
name: 'user_setting',
meta: {
requiresAuth: true
},
component: () => import('../views/user_setting.vue')
},
{
path: '/about',
name: 'about',
component: () => import('../views/about.vue')
},
{
path: '/wx',

@ -1,58 +1,45 @@
import {InjectionKey} from 'vue'
import {createStore, useStore as baseUseStore, Store} from 'vuex'
import api from "../api";
import router from "../router";
import {darkTheme} from 'naive-ui'
import {User, UserState} from './user'
export interface State {
export interface State extends Object {
oauuid: string
user: object
theme: string
user: UserState
apps: []
}
export const key: InjectionKey<Store<State>> = Symbol()
export const store = createStore<State>({
modules: {
user: User
},
// @ts-ignore
state: {
theme: 'light',
oauuid: '',
user: {}
},
getters: {
IsDark(state: any) {
return state.theme === 'dark'
},
GetTheme(state: any, getters) {
return getters.IsDark ? darkTheme : null
}
apps: []
},
getters: {},
mutations: {
setOA(state: any, data: any) {
state.oauuid = data.uuid
},
setTheme(state: any, t: string) {
state.theme = t
setApps(state: State, data: any) {
state.apps = data
}
},
actions: {
changeTheme(context) {
if (context.getters.IsDark) {
context.commit('setTheme', 'light')
} else {
context.commit('setTheme', 'dark')
}
},
fetchSelf({commit}) {
api.app.self().Start(d => {
commit('setOA', d)
})
},
handleLogout() {
localStorage.removeItem('auth_token')
router.push({name: 'login'})
fetchApps({commit}) {
api.app.list().Start(e => {
commit('setApps', e)
})
}
}
})

@ -0,0 +1,73 @@
import {Module} from "vuex";
import api from "../api";
import util from '../libs/util'
import {Base64} from 'js-base64'
import {State} from './index'
import router from "../router";
export interface UserState {
id: number
username: string
nickname: string
phone: string
icon: string
email: string
ready: boolean
auth: [auth?]
[key: string]: any
}
interface auth {
rid: string
ruid: string
level: number
}
export const User: Module<UserState, State> = {
namespaced: true,
state: {
id: 0,
username: '',
nickname: '',
phone: '',
icon: '',
email: '',
auth: [],
ready: false
},
mutations: {
setBase(state: UserState, data: any) {
state.id = data.id
state.icon = data.icon
state.username = data.username
state.nickname = data.nickname
state.phone = data.phone
state.email = data.email
state.ready = true
},
setAuth(state: UserState, data: any) {
state.auth = data
},
logout(state: UserState) {
state.ready = false
localStorage.removeItem('auth_token')
router.push({name: 'login'})
}
},
actions: {
fetchUserData(context) {
let token = util.getToken()?.split('.');
if (!token || token.length !== 3) {
return false
}
let data = JSON.parse(Base64.decode(token[1]))
if (data.id > 0) {
context.commit('setAuth', data.auth)
api.user.get(data.id).Start(e => {
context.commit('setBase', e)
})
}
}
}
}

@ -0,0 +1,72 @@
import {darkTheme} from 'naive-ui/lib/themes'
import {BuiltInGlobalTheme} from 'naive-ui/lib/themes/interface'
import {lightTheme} from 'naive-ui/lib/themes/light'
import {ref} from 'vue'
import {useOsTheme, GlobalThemeOverrides} from 'naive-ui'
interface builtIn extends BuiltInGlobalTheme {
overrides: GlobalThemeOverrides
me: {
lightBox: string,
lightBoxShadow: string
}
}
let light = lightTheme as builtIn
let dark = darkTheme as builtIn
let intputNone = {
color: 'url(0) no-repeat',
colorFocus: 'url(0) no-repeat',
colorFocusWarning: 'url(0) no-repeat',
colorFocusError: 'url(0) no-repeat'
}
light.overrides = {
Input: Object.assign({}, intputNone)
}
dark.overrides = {
Input: Object.assign({
border: '1px solid #aaa'
}, intputNone)
}
light.common.cardColor = '#f4f4f4'
light.common.bodyColor = '#eee'
dark.common.bodyColor = '#2e2e2e'
light.me = {
lightBox: '#f4f4f4',
lightBoxShadow: '18px 18px 36px #c6c6c6, -18px -18px 36px #fff'
}
dark.me = {
lightBox: '#2e2e2e',
lightBoxShadow: '21px 21px 42px #272727, -21px -21px 42px #353535'
}
export const OsThemeRef = useOsTheme()
let theme = 'light'
export let Theme = ref(light)
export let IsDark = ref(false)
function change(t: string) {
if (t === 'dark') {
theme = 'dark'
Theme.value = dark
} else {
theme = 'light'
Theme.value = light
}
IsDark.value = theme === 'dark'
}
export function ChangeTheme() {
if (IsDark.value) {
change('light')
} else {
change('dark')
}
}
if (OsThemeRef.value === 'dark') {
change('dark')
}

@ -0,0 +1,12 @@
<template>
<div>
about
</div>
</template>
<script lang="ts" setup>
</script>
<style scoped>
</style>

@ -0,0 +1,28 @@
<template>
<div>
{{ uuid }}
</div>
</template>
<script lang="ts" setup>
import {useRoute, useRouter} from "vue-router";
import {computed, onMounted} from "vue";
import api from "../api";
let route = useRoute()
let router = useRouter()
let uuid = computed(() => route.params.uuid)
onMounted(() => {
if (uuid.value === '') {
router.push({name: '404', params: {path: route.path}})
return
}
api.app.get(uuid.value as string).Start(e => {
console.log(e)
})
})
</script>
<style scoped>
</style>

@ -1,11 +1,18 @@
<template>
<div>
<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 class="flex items-center justify-center" v-for="(item, k) in apps" :key="k">
<AppCard :core="item"></AppCard>
</div>
<div class="flex items-center justify-center" v-for="(item) in '1234567890'" :key="item">
<AppCard :core="{}"></AppCard>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref} from "vue";
import {onMounted, ref} from "vue";
import api from "../api";
import AppCard from '../components/app.vue'
let apps = ref([])
@ -15,7 +22,9 @@ function getApps() {
})
}
getApps()
onMounted(() => {
getApps()
})
</script>

@ -1,61 +1,98 @@
<template>
<div class="flex items-center justify-center">
<div class="p-3" style="">
<n-form ref="formRef" label-placement="left">
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
<n-input v-model:value="data.username"></n-input>
<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="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
<n-input v-model:value="data.username"></n-input>
<n-form-item required label="密码" path="password">
<n-input @keydown.enter="login" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
type="password"></n-input>
</n-form-item>
<n-button @click="login"></n-button>
<div class="flex justify-around mt-4">
<n-button @click="login"></n-button>
<n-button @click="router.push({name:'register'})"></n-button>
</div>
</n-form>
</div>
</div>
</template>
<script lang="ts" setup>
import {computed, ref} from "vue";
import {computed, onMounted, ref} from "vue";
import {Theme} from "../theme";
import {useMessage} from 'naive-ui'
import api from "../api"
import {useRoute, useRouter} from "vue-router";
import {store} from "../store";
let msg = useMessage()
const route = useRoute()
const router = useRouter()
let formRef = ref(null)
const divs = ref([])
let form_ref = ref(null)
let data = ref({
username: null,
password: null
username: '',
password: ''
})
let ruleInline = {
let rules = {
username: [
(v: string) => !!v || 'required',
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16'
{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
},
trigger: ['input', 'blur']
}
],
password: [
(v: string) => !!v || 'required',
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16'
]
}
function check(rs: [], v: any) {
for (let r of rs) {
let res = r(v)
if (res !== true) {
return ['error', res]
password: [{
required: true,
validator(r: any, v: any) {
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
}
}
return ['', '']
}]
}
let rules = ref({
username: computed(() => {
return check(ruleInline.username, data.value.username)
})
let uuid = computed(() => {
return route.params.uuid || store.state.oauuid
})
function login() {
formRef.value.validate(e => {
console.log(e)
// @ts-ignore
form_ref.value.validate((e:any) => {
if (!e) {
api.user.login(data.value.username, data.value.password, uuid.value as string).Start((url: string) => {
msg.success('登录成功')
store.dispatch('user/fetchUserData')
let target = url
if (route.query.redirect) {
target = route.query.redirect as string
}
if (target && target.startsWith('http')) {
window.location.href = target
} else if (target) {
router.push(target)
} else {
router.push({name: 'home'})
}
}, e => {
console.log(e)
msg.warning('登录失败:' + e)
})
}
})
}
onMounted(() => {
if (divs.value[0]) {
// @ts-ignore
divs.value[0].focus()
}
})
</script>
<style scoped>
</style>

@ -0,0 +1,96 @@
<template>
<div class="pt-10">
<div class="flex justify-center">
<div class="relative rounded-xl text-lg text-black" :style="{background: IsDark?'#555': '#d5d5d5'}">
<div @click="ifInfo=true" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '#fc0005': ''}">
个人信息
</div>
<div @click="ifInfo=false" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '': '#fc0005'}">
账户管理
</div>
</div>
</div>
<div class="inline-block flex justify-center mt-10">
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
<div v-if="ifInfo" class="animate__animated animate__faster">
<n-form label-placement="left" label-width="80px" label-align="left">
<n-form-item label="昵称">
<n-input v-model:value="user.nickname" @blur="update('nickname')"></n-input>
</n-form-item>
<n-form-item label="头像">
<n-upload
action=""
:headers="{'': ''}"
:data="{}"
>
<n-avatar size="large" round :src="user.icon">
</n-avatar>
</n-upload>
</n-form-item>
</n-form>
</div>
<div v-else class="animate__animated animate__faster">
<n-form label-align="left" label-width="80px" label-placement="left">
<n-form-item label="username">
<n-input disabled v-model:value="user.username"></n-input>
</n-form-item>
<n-form-item label="phone">
<n-input v-model:value="user.phone" @blur="update('phone')"></n-input>
</n-form-item>
<n-form-item label="email">
<n-auto-complete :options="emailOptions" v-model:value="user.email"
@blur="update('email')"></n-auto-complete>
</n-form-item>
</n-form>
</div>
</transition>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, computed} from "vue";
import {IsDark} from "../theme";
import {useStore} from "../store";
import api from "../api";
import {useMessage} from "naive-ui";
let msg = useMessage()
let store = useStore()
let ifInfo = ref(true)
let user = ref({
username: store.state.user.username,
nickname: store.state.user.nickname,
icon: store.state.user.icon,
email: store.state.user.email,
phone: store.state.user.phone,
})
let emailOptions = computed(() => {
return ['@gmail.com', '@163.com', '@qq.com'].map((suffix) => {
const prefix = user.value.email.split('@')[0]
return {
label: prefix + suffix,
value: prefix + suffix
}
})
})
function update(key: string) {
// @ts-ignore
let v = user.value[key]
if (v === store.state.user[key]) {
return
}
api.user.update(store.state.user.id, {[key]: v}).Start(e => {
msg.success('更新成功')
store.state.user[key] = v
}, e => {
msg.error('更新失败: ' + e.err)
})
}
</script>
<style scoped>
</style>

14
oaf/src/vuex.d.ts vendored

@ -0,0 +1,14 @@
import {ComponentCustomProperties} from 'vue'
import {Store} from 'vuex'
import {State as root} from './store'
declare module '@vue/runtime-core' {
// 声明自己的 store state
interface State extends root {
}
// 为 `this.$store` 提供类型声明
interface ComponentCustomProperties {
$store: Store<State>
}
}

@ -1,19 +1,36 @@
import { defineConfig } from 'vite'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
// host: '0.0.0.0',
host: '127.0.0.1',
port: 8080,
proxy: {
'/api': 'http://127.0.0.1:4001/'
plugins: [vue()],
server: {
// host: '0.0.0.0',
host: '127.0.0.1',
port: 8080,
proxy: {
'/api': {
target: 'http://127.0.0.1:4001/',
changeOrigin: true,
ws: true
},
'/media': {
target: 'http://127.0.0.1:4001/',
changeOrigin: true,
ws: true
}
}
},
build: {
outDir: '../sub/static/',
assetsDir: './',
rollupOptions: {
output: {
// 重点在这里哦
entryFileNames: `static/[name].[hash].js`,
chunkFileNames: `static/[name].[hash].js`,
assetFileNames: `static/[name].[hash].[ext]`
}
}
}
},
build: {
outDir: '../build/static/',
assetsDir: 'assets'
}
})

@ -216,10 +216,10 @@
dependencies:
"@types/yargs-parser" "*"
"@veypi/one-icon@2":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.0.tgz#15262e87644903d90a2124fbc9c95fbb6bb6bcb8"
integrity sha512-zcUx3YzTIRiZe5XkJRZyKDgVzQThEWyZSCpQTreeYtKEObCl4B0Oa6CFYo4IToGD6uQmReM0R6oNPg5mhPjc2A==
"@veypi/one-icon@2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.5.tgz#1083be30fc3bbb89aaf19501b00110d3ae9b1c77"
integrity sha512-THnQh1zbH+glwDBLjP7rtcF3UtTQnzsxnUQEtUjAjv9Eo6rVSa1u4RsFA8CMDpoVcQiTMhqgRalbfjovdr11wg==
dependencies:
vue "^3.2.20"
@ -396,6 +396,11 @@ acorn@^7.0.0, acorn@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
animate.css@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075"
integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==
ansi-regex@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"

@ -5,7 +5,6 @@ import (
"OneAuth/libs/auth"
"OneAuth/models"
"github.com/urfave/cli/v2"
"github.com/veypi/utils/cmd"
"github.com/veypi/utils/log"
"strconv"
)
@ -20,6 +19,7 @@ func runInit(c *cli.Context) error {
}
// 初始化项目
var appid uint
func InitSystem() error {
db()
@ -27,15 +27,9 @@ func InitSystem() error {
if err != nil {
return err
}
cfg.CFG.APPID = self.ID
cfg.CFG.APPKey = self.Key
err = cmd.DumpCfg(cfg.Path, cfg.CFG)
// TODO
//if err != nil {
// return err
//}
appid = self.ID
err = role(self.InitRoleID == 0)
return nil
return err
}
func db() {
@ -56,14 +50,14 @@ func selfApp() (*models.App, error) {
self := &models.App{
Name: "OA",
Icon: "",
UUID: "jU5Jo5hM",
UUID: cfg.CFG.APPUUID,
Des: "",
Creator: 0,
UserCount: 0,
Hide: false,
Host: "",
UserRefreshUrl: "/",
Key: "cB43wF94MLTksyBK",
Key: cfg.CFG.APPKey,
EnableRegister: true,
EnableUserKey: true,
EnableUser: true,
@ -86,7 +80,7 @@ func role(reset_init_role bool) error {
}
var err error
adminRole := &models.Role{
AppID: cfg.CFG.APPID,
AppID: appid,
Name: "admin",
IsUnique: false,
}
@ -96,7 +90,7 @@ func role(reset_init_role bool) error {
}
for _, na := range n {
a := &models.Resource{
AppID: cfg.CFG.APPID,
AppID: appid,
Name: na,
Tag: "",
Des: "",
@ -112,7 +106,7 @@ func role(reset_init_role bool) error {
}
}
userRole := &models.Role{
AppID: cfg.CFG.APPID,
AppID: appid,
Name: "user",
IsUnique: false,
}
@ -120,12 +114,12 @@ func role(reset_init_role bool) error {
if err != nil {
return err
}
err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(cfg.CFG.APPID)))
err = auth.BindRoleAuth(cfg.DB(), userRole.ID, authMap[auth.APP].ID, models.AuthRead, strconv.Itoa(int(appid)))
if err != nil {
return err
}
if reset_init_role {
return cfg.DB().Model(&models.App{}).Where("id = ?", cfg.CFG.APPID).Update("init_role_id", adminRole.ID).Error
return cfg.DB().Model(&models.App{}).Where("id = ?", appid).Update("init_role_id", adminRole.ID).Error
}
return nil
}

@ -1 +1,17 @@
<!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"><link rel="icon" href="/favicon.ico"><title>oaf</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"><link href="/css/chunk-6cfd0404.487f8d50.css" rel="prefetch"><link href="/js/about.a3055c06.js" rel="prefetch"><link href="/js/chunk-6cfd0404.65d5baaf.js" rel="prefetch"><link href="/app.b131f8d0f62acd99ab8e.js" rel="preload" as="script"><link href="/css/app.dafd5329.css" rel="preload" as="style"><link href="/css/chunk-vendors.dfe6062e.css" rel="preload" as="style"><link href="/js/chunk-vendors.83bac771.js" rel="preload" as="script"><link href="/css/chunk-vendors.dfe6062e.css" rel="stylesheet"><link href="/css/app.dafd5329.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but oaf doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.83bac771.js"></script><script src="/app.b131f8d0f62acd99ab8e.js"></script></body></html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<script type="module" crossorigin src="/static/index.dddecd43.js"></script>
<link rel="modulepreload" href="/static/vendor.ba3bd51d.js">
<link rel="stylesheet" href="/static/vendor.3a295b6b.css">
<link rel="stylesheet" href="/static/index.c49db26f.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

@ -6,16 +6,15 @@ import (
"embed"
"github.com/urfave/cli/v2"
"github.com/veypi/OneBD"
"github.com/veypi/OneBD/core"
"github.com/veypi/OneBD/rfc"
"github.com/veypi/utils/log"
"net/http"
"os"
)
//go:embed static
//go:embed static/static
var staticFiles embed.FS
//go:embed static/favicon.ico
var icon []byte
//go:embed static/index.html
var indexFile []byte
@ -37,34 +36,15 @@ func RunWeb(c *cli.Context) error {
LoggerPath: cfg.CFG.LoggerPath,
LoggerLevel: ll,
})
app.Router().EmbedFile("/", indexFile)
app.Router().EmbedDir("/", staticFiles, "static/")
// TODO media 文件需要检验权限
//app.Router().SubRouter("/media/").Static("/", cfg.CFG.EXEDir+"/media")
app.Router().SetNotFoundFunc(func(m core.Meta) {
f, err := os.Open(cfg.CFG.EXEDir + "/static/index.html")
if err != nil {
m.WriteHeader(rfc.StatusNotFound)
return
}
defer f.Close()
info, err := f.Stat()
if err != nil {
m.WriteHeader(rfc.StatusNotFound)
return
}
if info.IsDir() {
// TODO:: dir list
m.WriteHeader(rfc.StatusNotFound)
return
}
http.ServeContent(m, m.Request(), info.Name(), info.ModTime(), f)
})
api.Router(app.Router().SubRouter("api"))
// TODO media 文件需要检验权限
app.Router().SubRouter("/media/").Static("/", cfg.CFG.MediaDir)
app.Router().EmbedDir("/static", staticFiles, "static/static/")
app.Router().EmbedFile("/favicon.ico", icon)
app.Router().EmbedFile("/*", indexFile)
log.Info().Msg("\nRouting Table\n" + app.Router().String())
return app.Run()
}

Loading…
Cancel
Save