add organize opera
This commit is contained in:
parent
0d4039cf34
commit
b49c431a8e
91
api.go
91
api.go
|
@ -3,9 +3,15 @@ package moto
|
|||
import (
|
||||
"git.nobla.cn/golang/kos"
|
||||
"git.nobla.cn/golang/kos/entry/http"
|
||||
"git.nobla.cn/golang/moto/internal/user"
|
||||
"git.nobla.cn/golang/moto/internal/user/passport"
|
||||
"git.nobla.cn/golang/moto/internal/user/types"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/internal/organize"
|
||||
"git.nobla.cn/golang/moto/internal/organize/passport"
|
||||
"git.nobla.cn/golang/moto/internal/organize/types"
|
||||
"git.nobla.cn/golang/moto/version"
|
||||
"git.nobla.cn/golang/rest"
|
||||
restTypes "git.nobla.cn/golang/rest/types"
|
||||
"gorm.io/gorm"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (svr *Server) handleLogin(ctx *http.Context) (err error) {
|
||||
|
@ -32,7 +38,7 @@ func (svr *Server) handleProfile(ctx *http.Context) (err error) {
|
|||
var (
|
||||
profile *types.Profile
|
||||
)
|
||||
if profile, err = user.Profile(ctx.Context(), ctx.User().ID); err != nil {
|
||||
if profile, err = organize.Profile(ctx.Context(), ctx.User().ID); err != nil {
|
||||
return ctx.Error(http.ErrTemporaryUnavailable, err.Error())
|
||||
}
|
||||
return ctx.Success(profile)
|
||||
|
@ -42,12 +48,85 @@ func (svr *Server) handleGetConfigure(ctx *http.Context) (err error) {
|
|||
return ctx.Success(map[string]string{})
|
||||
}
|
||||
|
||||
func (svr *Server) handleListSchema(ctx *http.Context) (err error) {
|
||||
var (
|
||||
schemas []*restTypes.Schema
|
||||
)
|
||||
scenario := ctx.Query("scenario")
|
||||
if scenario == "" {
|
||||
schemas, err = rest.GetSchemas(
|
||||
ctx.Request().Context(),
|
||||
db.WithContext(ctx.Request().Context()),
|
||||
ctx.User().Get("domain"),
|
||||
version.ProductName,
|
||||
ctx.Param("table"),
|
||||
)
|
||||
} else {
|
||||
schemas, err = rest.VisibleSchemas(
|
||||
ctx.Request().Context(),
|
||||
db.WithContext(ctx.Request().Context()),
|
||||
ctx.User().Get("domain"),
|
||||
version.ProductName,
|
||||
ctx.Param("table"),
|
||||
scenario,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return ctx.Error(http.ErrResourceNotFound, err.Error())
|
||||
} else {
|
||||
return ctx.Success(schemas)
|
||||
}
|
||||
}
|
||||
|
||||
func (svr *Server) handleSaveSchema(ctx *http.Context) (err error) {
|
||||
schemas := make([]*restTypes.Schema, 0)
|
||||
if err = ctx.Bind(&schemas); err != nil {
|
||||
return ctx.Error(http.ErrInvalidPayload, err.Error())
|
||||
}
|
||||
domainName := ctx.User().Get("domain")
|
||||
for i, _ := range schemas {
|
||||
schemas[i].Domain = domainName
|
||||
}
|
||||
if err = db.WithContext(ctx.Request().Context()).Transaction(func(tx *gorm.DB) (errTx error) {
|
||||
for _, scm := range schemas {
|
||||
if errTx = tx.Save(scm).Error; errTx != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}); err == nil {
|
||||
return ctx.Success(map[string]interface{}{
|
||||
"count": len(schemas),
|
||||
"state": "success",
|
||||
})
|
||||
} else {
|
||||
return ctx.Error(http.ErrTemporaryUnavailable, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (svr *Server) handleDeleteSchema(ctx *http.Context) (err error) {
|
||||
id, _ := strconv.Atoi(ctx.Param("id"))
|
||||
model := &restTypes.Schema{Id: uint64(id)}
|
||||
if err = db.WithContext(ctx.Request().Context()).Delete(model).Error; err == nil {
|
||||
return ctx.Success(map[string]any{
|
||||
"id": id,
|
||||
})
|
||||
} else {
|
||||
return ctx.Error(http.ErrResourceDelete, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (svr *Server) routes() {
|
||||
kos.Http().Use(user.AuthMiddleware)
|
||||
user.AllowUri("/passport/login")
|
||||
kos.Http().Use(organize.AuthMiddleware)
|
||||
organize.AllowUri("/passport/login")
|
||||
|
||||
kos.Http().Handle(http.MethodPost, "/passport/login", svr.handleLogin)
|
||||
kos.Http().Handle(http.MethodDelete, "/passport/logout", svr.handleLogout)
|
||||
kos.Http().Handle(http.MethodGet, "/user/profile", svr.handleProfile)
|
||||
kos.Http().Handle(http.MethodGet, "/user/configures", svr.handleGetConfigure)
|
||||
|
||||
kos.Http().Handle(http.MethodGet, "/rest/schema/:module/:table", svr.handleListSchema)
|
||||
kos.Http().Handle(http.MethodPut, "/rest/schema/:module/:table", svr.handleSaveSchema)
|
||||
kos.Http().Handle(http.MethodDelete, "/rest/schema/:id", svr.handleDeleteSchema)
|
||||
|
||||
}
|
||||
|
|
|
@ -5,8 +5,10 @@ import (
|
|||
"git.nobla.cn/golang/moto/config"
|
||||
"git.nobla.cn/golang/rest/types"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
lru "github.com/hashicorp/golang-lru/v2"
|
||||
mysqlDriver "gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"reflect"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -14,6 +16,73 @@ var (
|
|||
db *gorm.DB
|
||||
)
|
||||
|
||||
var (
|
||||
cacheInstance, _ = lru.New[string, *cacheEntry](64)
|
||||
)
|
||||
|
||||
func WithDepend(s string, args ...any) CacheOption {
|
||||
return func(o *CacheOptions) {
|
||||
o.dependSQL = s
|
||||
o.dependArgs = args
|
||||
}
|
||||
}
|
||||
|
||||
func TryCache(ctx context.Context, key string, f CachingFunc, cbs ...CacheOption) (value any, err error) {
|
||||
var (
|
||||
ok bool
|
||||
hasDependValue bool
|
||||
dependValue any
|
||||
)
|
||||
opts := &CacheOptions{}
|
||||
for _, cb := range cbs {
|
||||
cb(opts)
|
||||
}
|
||||
//从缓存加载数据
|
||||
if value, ok = cacheInstance.Get(key); ok {
|
||||
entry := value.(*cacheEntry)
|
||||
if opts.dependSQL == "" {
|
||||
return entry.storeValue, nil
|
||||
}
|
||||
//如果频繁访问,不检查依赖
|
||||
if time.Since(entry.lastChecked) < time.Millisecond*500 {
|
||||
return entry.storeValue, nil
|
||||
}
|
||||
//对比依赖值
|
||||
if err = WithContext(ctx).Raw(opts.dependSQL, opts.dependArgs...).Scan(&dependValue).Error; err == nil {
|
||||
hasDependValue = true
|
||||
if reflect.DeepEqual(entry.compareValue, dependValue) {
|
||||
entry.lastChecked = time.Now()
|
||||
return entry.storeValue, nil
|
||||
} else {
|
||||
cacheInstance.Remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
//从数据库加载数据
|
||||
if value, err = f(WithContext(ctx)); err == nil {
|
||||
if !hasDependValue {
|
||||
if err = WithContext(ctx).Raw(opts.dependSQL, opts.dependArgs...).Scan(&dependValue).Error; err == nil {
|
||||
cacheInstance.Add(key, &cacheEntry{
|
||||
compareValue: dependValue,
|
||||
storeValue: value,
|
||||
createdAt: time.Now(),
|
||||
lastChecked: time.Now(),
|
||||
})
|
||||
}
|
||||
} else {
|
||||
cacheInstance.Add(key, &cacheEntry{
|
||||
compareValue: dependValue,
|
||||
storeValue: value,
|
||||
createdAt: time.Now(),
|
||||
lastChecked: time.Now(),
|
||||
})
|
||||
}
|
||||
return value, nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
func Init(ctx context.Context, cfg config.Database, plugins ...gorm.Plugin) (err error) {
|
||||
dbCfg := &mysql.Config{
|
||||
Net: "tcp",
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
CachingFunc func(tx *gorm.DB) (any, error)
|
||||
|
||||
CacheOptions struct {
|
||||
dependSQL string
|
||||
dependArgs []any
|
||||
}
|
||||
|
||||
CacheOption func(o *CacheOptions)
|
||||
|
||||
cacheEntry struct {
|
||||
storeValue any
|
||||
compareValue any
|
||||
createdAt time.Time
|
||||
lastChecked time.Time
|
||||
}
|
||||
)
|
12
go.mod
12
go.mod
|
@ -4,9 +4,10 @@ go 1.22.9
|
|||
|
||||
require (
|
||||
git.nobla.cn/golang/kos v0.1.32
|
||||
git.nobla.cn/golang/rest v0.0.1
|
||||
git.nobla.cn/golang/rest v0.0.2
|
||||
github.com/go-sql-driver/mysql v1.8.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/mssola/useragent v1.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
|
@ -15,14 +16,21 @@ require (
|
|||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.3 // indirect
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
|
||||
github.com/peterh/liner v1.2.2 // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/stretchr/testify v1.8.4 // indirect
|
||||
golang.org/x/crypto v0.19.0 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.17.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -2,16 +2,28 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
|||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
git.nobla.cn/golang/kos v0.1.32 h1:sFVCA7vKc8dPUd0cxzwExOSPX2mmMh2IuwL6cYS1pBc=
|
||||
git.nobla.cn/golang/kos v0.1.32/go.mod h1:35Z070+5oB39WcVrh5DDlnVeftL/Ccmscw2MZFe9fUg=
|
||||
git.nobla.cn/golang/rest v0.0.1 h1:atEF73F7NuzYGWzO4+H2qHwgtwV+omG1paEj1DJ5RN8=
|
||||
git.nobla.cn/golang/rest v0.0.1/go.mod h1:tGDOul2GGJtxk6fAeu+YLpMt/Up/TsBonTkygymN/wE=
|
||||
git.nobla.cn/golang/rest v0.0.2 h1:b5hmGuVb1zXFQ8O2AYCi8628JGftgH2TL5BLftCN1OU=
|
||||
git.nobla.cn/golang/rest v0.0.2/go.mod h1:tGDOul2GGJtxk6fAeu+YLpMt/Up/TsBonTkygymN/wE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
|
||||
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
|
@ -20,6 +32,8 @@ github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
|
||||
|
@ -32,10 +46,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
package organize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/models"
|
||||
"git.nobla.cn/golang/rest/types"
|
||||
"gorm.io/gorm"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RecursiveDepartment(ctx context.Context, parent string, level int, departments []*models.Department) []*types.TypeValue {
|
||||
var (
|
||||
child []*types.TypeValue
|
||||
)
|
||||
values := make([]*types.TypeValue, 0)
|
||||
for _, dept := range departments {
|
||||
if dept.Parent == parent {
|
||||
values = append(values, &types.TypeValue{
|
||||
Label: "|-" + strings.Repeat("--", level) + dept.Name,
|
||||
Value: dept.ID,
|
||||
})
|
||||
child = RecursiveDepartment(ctx, dept.ID, level+1, departments)
|
||||
if len(child) > 0 {
|
||||
for _, row := range child {
|
||||
values = append(values, row)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func RecursiveNestedDepartment(ctx context.Context, parent string, level int, departments []*models.Department) []*types.NestedValue {
|
||||
values := make([]*types.NestedValue, 0)
|
||||
for _, dept := range departments {
|
||||
if dept.Parent == parent {
|
||||
v := &types.NestedValue{
|
||||
Label: dept.Name,
|
||||
Value: dept.ID,
|
||||
Children: RecursiveNestedDepartment(ctx, dept.ID, level+1, departments),
|
||||
}
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func DepartmentUserNested(ctx context.Context, domainName string) []*types.NestedValue {
|
||||
var (
|
||||
err error
|
||||
value any
|
||||
users []*models.User
|
||||
departments []*models.Department
|
||||
)
|
||||
|
||||
if value, err = db.TryCache(ctx, fmt.Sprintf("domain:%s:departments", domainName), func(tx *gorm.DB) (any, error) {
|
||||
departments = make([]*models.Department, 0)
|
||||
err = tx.Where("domain=?", domainName).Order("created_at ASC").Find(&departments).Error
|
||||
return departments, err
|
||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `departments` WHERE `domain`=?", domainName)); err == nil {
|
||||
departments = value.([]*models.Department)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if value, err = db.TryCache(ctx, fmt.Sprintf("domain:%s:users", domainName), func(tx *gorm.DB) (any, error) {
|
||||
users = make([]*models.User, 0)
|
||||
err = tx.Where("domain=?", domainName).Order("uid ASC").Find(&users).Error
|
||||
return users, err
|
||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `users` WHERE `domain`=?", domainName)); err == nil {
|
||||
users = value.([]*models.User)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
depts := RecursiveDepartment(ctx, "", 0, departments)
|
||||
values := make([]*types.NestedValue, 0)
|
||||
for _, dept := range depts {
|
||||
v := &types.NestedValue{
|
||||
Label: dept.Label,
|
||||
Value: dept.Value,
|
||||
Children: make([]*types.NestedValue, 0),
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.Department == v.Value {
|
||||
v.Children = append(v.Children, &types.NestedValue{
|
||||
Label: fmt.Sprintf("%s(%s)", user.Username, user.UID),
|
||||
Value: user.UID,
|
||||
})
|
||||
}
|
||||
}
|
||||
values = append(values, v)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func DepartmentTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
||||
result, err := db.TryCache(ctx, fmt.Sprintf("domain:%s:department:types", domainName), func(tx *gorm.DB) (any, error) {
|
||||
values := make([]*models.Department, 0)
|
||||
if err := db.WithContext(ctx).Where("domain=?", domainName).Find(&values).Error; err == nil {
|
||||
return RecursiveDepartment(ctx, "", 0, values), nil
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `departments` WHERE `domain`=?", domainName))
|
||||
if err == nil {
|
||||
return result.([]*types.TypeValue)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
package user
|
||||
package organize
|
||||
|
||||
import (
|
||||
"git.nobla.cn/golang/kos/entry/http"
|
||||
"git.nobla.cn/golang/moto/internal/user/passport"
|
||||
"git.nobla.cn/golang/moto/internal/user/types"
|
||||
"git.nobla.cn/golang/moto/internal/organize/passport"
|
||||
"git.nobla.cn/golang/moto/internal/organize/types"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"git.nobla.cn/golang/kos/pkg/cache"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/internal/user/types"
|
||||
"git.nobla.cn/golang/moto/internal/organize/types"
|
||||
"git.nobla.cn/golang/moto/models"
|
||||
"github.com/mssola/useragent"
|
||||
"strings"
|
|
@ -1,9 +1,9 @@
|
|||
package user
|
||||
package organize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/internal/user/types"
|
||||
"git.nobla.cn/golang/moto/internal/organize/types"
|
||||
)
|
||||
|
||||
func Profile(ctx context.Context, uid string) (profile *types.Profile, err error) {
|
|
@ -0,0 +1,21 @@
|
|||
package organize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/models"
|
||||
"git.nobla.cn/golang/rest"
|
||||
"git.nobla.cn/golang/rest/types"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func RoleTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
||||
result, err := db.TryCache(ctx, fmt.Sprintf("domain:%s:role:types", domainName), func(tx *gorm.DB) (any, error) {
|
||||
return rest.ModelTypes(ctx, tx, &models.Role{}, domainName, "name", "id"), nil
|
||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `roles` WHERE `domain`=?", domainName))
|
||||
if err == nil {
|
||||
return result.([]*types.TypeValue)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package organize
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/models"
|
||||
"git.nobla.cn/golang/rest/types"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func UserTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
||||
result, err := db.TryCache(ctx, fmt.Sprintf("domain:%s:user:types", domainName), func(tx *gorm.DB) (any, error) {
|
||||
values := make([]*models.User, 0)
|
||||
err := tx.Where("domain=?", domainName).Order("uid ASC").Find(&values).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*types.TypeValue, 0, len(values))
|
||||
for _, row := range values {
|
||||
data = append(data, &types.TypeValue{
|
||||
Label: fmt.Sprintf("%s(%s)", row.Username, row.UID),
|
||||
Value: row.UID,
|
||||
})
|
||||
}
|
||||
return data, nil
|
||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `users` WHERE `domain`=?", domainName))
|
||||
if err == nil {
|
||||
return result.([]*types.TypeValue)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -7,8 +7,9 @@ import (
|
|||
"git.nobla.cn/golang/moto/common/db"
|
||||
"git.nobla.cn/golang/moto/config"
|
||||
"git.nobla.cn/golang/moto/models"
|
||||
"git.nobla.cn/golang/moto/version"
|
||||
"git.nobla.cn/golang/rest"
|
||||
"git.nobla.cn/golang/rest/plugins/identity"
|
||||
"git.nobla.cn/golang/rest/plugins/validate"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
@ -20,7 +21,7 @@ type Server struct {
|
|||
}
|
||||
|
||||
func (svr *Server) prepare() (err error) {
|
||||
if err = db.Init(svr.ctx, svr.cfg.Database); err != nil {
|
||||
if err = db.Init(svr.ctx, svr.cfg.Database, identity.New(), validate.New()); err != nil {
|
||||
return
|
||||
}
|
||||
values := []any{
|
||||
|
@ -29,15 +30,15 @@ func (svr *Server) prepare() (err error) {
|
|||
&models.Login{},
|
||||
&models.Department{},
|
||||
}
|
||||
rest.SetHttpRouter(svr)
|
||||
for _, item := range values {
|
||||
if err = db.DB().AutoMigrate(item); err != nil {
|
||||
return
|
||||
}
|
||||
if err = rest.AutoMigrate(svr.ctx, db.DB(), item, rest.WithoutDomain(), rest.WithModuleName(version.ProductName)); err != nil {
|
||||
if err = rest.AutoMigrate(svr.ctx, db.DB(), item, rest.WithoutDomain(), rest.WithModuleName("rest")); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
rest.SetHttpRouter(svr)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,9 @@ const props = defineProps({
|
|||
},
|
||||
tableName: {
|
||||
type: String,
|
||||
},
|
||||
apiPrefix: {
|
||||
type: String,
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -27,7 +30,7 @@ const loading = ref(false)
|
|||
const crud = ref(new CRUD({
|
||||
moduleName: props.moduleName,
|
||||
tableName: props.tableName,
|
||||
apiPrefix: ''
|
||||
apiPrefix: props.apiPrefix
|
||||
}))
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
@ -25,8 +25,8 @@
|
|||
</div>
|
||||
<div class="segment-body" :class="disableHeader ? 'plain' : ''">
|
||||
<div class="segment-search">
|
||||
<active-form :schemas="schemas" :size="size" scenario="search" :model="searchModel" :inline="true"
|
||||
:actions="searchButtons" :auto-commit="autoQuery">
|
||||
<active-form :schemas="schemas" :size="size" scenario="search" :model="searchModel"
|
||||
:inline="true" :actions="searchButtons" :auto-commit="autoQuery">
|
||||
<template #default="{ model, schema }">
|
||||
<slot name="searchform" :model="model" :schema="schema"></slot>
|
||||
</template>
|
||||
|
@ -73,7 +73,8 @@
|
|||
</el-drawer>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-dialog v-model="formVisible" :title="formTitle" :width="formWidth" draggable :destroy-on-close="true">
|
||||
<el-dialog v-model="formVisible" :title="formTitle" :width="formWidth" draggable
|
||||
:destroy-on-close="true">
|
||||
<active-form :schemas="schemas" :size="size" :scenario="formScenario" :model="formModel"
|
||||
:actions="formButtons">
|
||||
<template #default="{ model, schema }">
|
||||
|
@ -138,6 +139,10 @@ const props = defineProps({
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
apiPrefix: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
disableToolbar: { //禁用按钮
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
|
|
@ -7,13 +7,14 @@ class CRUD {
|
|||
opts = Object.assign({
|
||||
moduleName: '',
|
||||
tableName: '',
|
||||
apiPrefix: '/v1',
|
||||
apiPrefix: '',
|
||||
schemas: [],
|
||||
}, opts);
|
||||
this.opts = opts
|
||||
this.primaryKey = ''
|
||||
this.modelName = opts.moduleName
|
||||
this.tableName = opts.tableName
|
||||
this.apiPrefix = opts.apiPrefix || opts.moduleName || 'rest'
|
||||
this.models = []
|
||||
this.sortable = []
|
||||
this.queryParams = {}
|
||||
|
@ -140,30 +141,31 @@ class CRUD {
|
|||
|
||||
__buildModelUri(moduleName, tableNanme, scenario, primaryKey) {
|
||||
primaryKey = primaryKey || ''
|
||||
let uri = this.opts.apiPrefix ? this.opts.apiPrefix : '';
|
||||
let uri = '';
|
||||
let apiPrefix = this.apiPrefix;
|
||||
let pluralizeName = pluralize.plural(tableNanme);
|
||||
let singularName = pluralize.singular(tableNanme);
|
||||
switch (scenario) {
|
||||
case 'create':
|
||||
uri += `/${moduleName}/${singularName}`
|
||||
uri += `/${apiPrefix}/${singularName}`
|
||||
break
|
||||
case 'update':
|
||||
uri += `/${moduleName}/${singularName}/${primaryKey}`
|
||||
uri += `/${apiPrefix}/${singularName}/${primaryKey}`
|
||||
break
|
||||
case 'delete':
|
||||
uri += `/${moduleName}/${singularName}/${primaryKey}`
|
||||
uri += `/${apiPrefix}/${singularName}/${primaryKey}`
|
||||
break
|
||||
case 'get':
|
||||
uri += `/${moduleName}/${singularName}/${primaryKey}`
|
||||
uri += `/${apiPrefix}/${singularName}/${primaryKey}`
|
||||
break
|
||||
case 'search':
|
||||
uri += `/${moduleName}/${pluralizeName}`
|
||||
uri += `/${apiPrefix}/${pluralizeName}`
|
||||
break
|
||||
case 'export':
|
||||
uri += `/${moduleName}/${singularName}-export`
|
||||
uri += `/${apiPrefix}/${singularName}-export`
|
||||
break
|
||||
case 'import':
|
||||
uri += `/${moduleName}/${singularName}-import`
|
||||
uri += `/${apiPrefix}/${singularName}-import`
|
||||
break
|
||||
}
|
||||
return uri
|
||||
|
|
|
@ -87,622 +87,6 @@ const menu = [
|
|||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "客户管理",
|
||||
icon: "group",
|
||||
hidden: false,
|
||||
route: "/customer",
|
||||
children: [
|
||||
{
|
||||
label: "联系人",
|
||||
navigation: true,
|
||||
route: "/customer/contacts",
|
||||
children: [
|
||||
{
|
||||
label: "新建联系人",
|
||||
hidden: true,
|
||||
route: "/customer/contact/create",
|
||||
view: '../views/customer/contact/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "编辑联系人",
|
||||
hidden: true,
|
||||
route: "/customer/contact/update",
|
||||
view: '../views/customer/contact/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "联系人详情",
|
||||
hidden: true,
|
||||
route: "/customer/contact/view",
|
||||
view: '../views/customer/contact/View.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
value: 'view'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "呼叫任务",
|
||||
icon: "outbound",
|
||||
hidden: false,
|
||||
route: "/outbounds",
|
||||
children: [
|
||||
{
|
||||
label: "全部任务",
|
||||
hidden: false,
|
||||
navigation: true,
|
||||
route: "/outbound/campaigns",
|
||||
children: [
|
||||
{
|
||||
label: "新建任务",
|
||||
hidden: true,
|
||||
route: "/outbound/campaign/create",
|
||||
view: '../views/outbound/campaign/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "编辑任务",
|
||||
hidden: true,
|
||||
route: "/outbound/campaign/update",
|
||||
view: '../views/outbound/campaign/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "任务详情",
|
||||
hidden: true,
|
||||
route: "/outbound/campaign/view",
|
||||
view: '../views/outbound/campaign/View.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
},
|
||||
{
|
||||
label: '详情',
|
||||
value: 'view'
|
||||
},
|
||||
{
|
||||
label: '启动',
|
||||
value: 'start'
|
||||
},
|
||||
{
|
||||
label: '暂停',
|
||||
value: 'pause'
|
||||
},
|
||||
{
|
||||
label: '停止',
|
||||
value: 'stop'
|
||||
},
|
||||
{
|
||||
label: '导入',
|
||||
value: 'import'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "记录报表",
|
||||
icon: "data",
|
||||
route: "/record",
|
||||
children: [
|
||||
{
|
||||
label: "话单记录",
|
||||
route: "/record/cdrs",
|
||||
children: [
|
||||
{
|
||||
label: "通话记录",
|
||||
route: "/record/cdr/logs"
|
||||
},
|
||||
{
|
||||
label: "来电未接",
|
||||
route: "/record/cdr/noanswers"
|
||||
},
|
||||
{
|
||||
label: "座席未接",
|
||||
route: "/record/cdr/usernoanswers"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "话单报表",
|
||||
route: "/record/reports",
|
||||
children: [
|
||||
{
|
||||
label: "座席报表",
|
||||
route: "/record/report/users"
|
||||
},
|
||||
{
|
||||
label: "呼入报表",
|
||||
route: "/record/report/inbounds"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "日志记录",
|
||||
route: "/record/log",
|
||||
children: [
|
||||
{
|
||||
label: "登录记录",
|
||||
route: "/record/log/logins"
|
||||
},
|
||||
{
|
||||
label: "考勤记录",
|
||||
route: "/record/log/attendances"
|
||||
},
|
||||
{
|
||||
label: "操作记录",
|
||||
route: "/record/log/activities"
|
||||
},
|
||||
{
|
||||
label: "导入导出",
|
||||
route: "/record/log/genfiles"
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "状态监控",
|
||||
icon: "jiankong",
|
||||
route: "/monitor",
|
||||
children: [
|
||||
{
|
||||
label: "座席监控",
|
||||
route: "/monitor/users",
|
||||
},
|
||||
{
|
||||
label: "话务监控",
|
||||
route: "/monitor/calls",
|
||||
},
|
||||
{
|
||||
label: "系统监控",
|
||||
hidden: true,
|
||||
route: "/monitor/systems",
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "系统配置",
|
||||
icon: "set",
|
||||
hidden: false,
|
||||
route: "/settings",
|
||||
children: [
|
||||
{
|
||||
label: "匹配规则",
|
||||
route: "/setting/rules",
|
||||
children: [
|
||||
{
|
||||
label: "号码匹配",
|
||||
navigation: true,
|
||||
route: "/setting/rule/numbersets",
|
||||
children: [
|
||||
{
|
||||
label: "号码列表",
|
||||
hidden: true,
|
||||
route: "/setting/rule/numberset/list"
|
||||
}
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "正则匹配",
|
||||
route: "/setting/rule/regexps",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "时间匹配",
|
||||
route: "/setting/rule/timeslots",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "地区匹配",
|
||||
route: "/setting/rule/zones",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "应用配置",
|
||||
route: "/setting/destinations",
|
||||
children: [
|
||||
{
|
||||
label: "语言导航",
|
||||
navigation: true,
|
||||
route: "/setting/destination/ivrs",
|
||||
children: [
|
||||
{
|
||||
label: "新建技能组",
|
||||
hidden: true,
|
||||
route: "/setting/destination/ivr/create",
|
||||
view: '../views/setting/destination/ivr/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "编辑技能组",
|
||||
hidden: true,
|
||||
route: "/setting/destination/ivr/update",
|
||||
view: '../views/setting/destination/ivr/Form.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "呼叫队列",
|
||||
navigation: true,
|
||||
route: "/setting/destination/queues",
|
||||
children: [
|
||||
{
|
||||
label: "新建队列",
|
||||
hidden: true,
|
||||
route: "/setting/destination/queue/create",
|
||||
view: '../views/setting/destination/queue/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "编辑队列",
|
||||
hidden: true,
|
||||
route: "/setting/destination/queue/update",
|
||||
view: '../views/setting/destination/queue/Form.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "语言文件",
|
||||
route: "/setting/destination/sounds",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "脚本程序",
|
||||
route: "/setting/destination/scripts",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "快捷功能",
|
||||
route: "/setting/destination/shortcuts",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "网关管理",
|
||||
navigation: true,
|
||||
route: "/setting/destination/gateways",
|
||||
children: [
|
||||
{
|
||||
label: "新建网关",
|
||||
hidden: true,
|
||||
route: "/setting/destination/gateway/create",
|
||||
view: '../views/setting/destination/gateway/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "编辑网关",
|
||||
hidden: true,
|
||||
route: "/setting/destination/gateway/update",
|
||||
view: '../views/setting/destination/gateway/Form.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "设备列表",
|
||||
route: "/setting/destination/devices",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "路由配置",
|
||||
route: "/setting/routes",
|
||||
children: [
|
||||
{
|
||||
label: "呼入路由",
|
||||
route: "/setting/route/inbounds",
|
||||
navigation: true,
|
||||
children: [
|
||||
{
|
||||
label: "新增路由",
|
||||
hidden: true,
|
||||
route: "/setting/route/inbound/create",
|
||||
view: '../views/setting/route/inbound/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "更新路由",
|
||||
hidden: true,
|
||||
route: "/setting/route/inbound/update",
|
||||
view: '../views/setting/route/inbound/Form.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "呼出路由",
|
||||
route: "/setting/route/outbounds",
|
||||
navigation: true,
|
||||
children: [
|
||||
{
|
||||
label: "新增路由",
|
||||
hidden: true,
|
||||
route: "/setting/route/outbound/create",
|
||||
view: '../views/setting/route/outbound/Form.vue'
|
||||
},
|
||||
{
|
||||
label: "更新路由",
|
||||
hidden: true,
|
||||
route: "/setting/route/outbound/update",
|
||||
view: '../views/setting/route/outbound/Form.vue'
|
||||
},
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "高级设置",
|
||||
route: "/setting/advanced",
|
||||
children: [
|
||||
{
|
||||
label: "访问控制",
|
||||
navigation: true,
|
||||
route: "/setting/advanced/acls",
|
||||
children: [
|
||||
{
|
||||
label: "规则列表",
|
||||
hidden: true,
|
||||
route: "/setting/advanced/acl/list"
|
||||
}
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "变量设置",
|
||||
route: "/setting/advanced/variables",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "状态设置",
|
||||
route: "/setting/advanced/statuses",
|
||||
permissions: [
|
||||
{
|
||||
label: '新建',
|
||||
value: 'create'
|
||||
},
|
||||
{
|
||||
label: '更新',
|
||||
value: 'update'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
value: 'delete'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "字段配置",
|
||||
hidden: true,
|
||||
route: "/setting/schemas",
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
export default menu;
|
|
@ -14,7 +14,7 @@ const props = defineProps({
|
|||
})
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return 'moto'
|
||||
return ''
|
||||
})
|
||||
|
||||
const tableName = computed(() => {
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
<template>
|
||||
<viewer :title="title" :module-name="moduleName" :table-name="tableName" :disable-toolbar="false"
|
||||
defaultSortable="id" formMode="drawer" :disablePermission="true">
|
||||
<template #crudform="{ schema, model }">
|
||||
<el-tree ref="treeElement" :data="permissions" v-if="schema.column == 'permissions'" node-key="id"
|
||||
v-model="model.permissions" :props="treeOptions" @check-change="handleTreeChange(model)"
|
||||
:default-checked-keys="getCheckedKeys(model)" show-checkbox></el-tree>
|
||||
</template>
|
||||
</viewer>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.el-tree {
|
||||
width: 100%;
|
||||
|
||||
.el-tree-node {
|
||||
&.permission-node {
|
||||
display: inline-block;
|
||||
|
||||
&:not(:first-child) {
|
||||
.el-tree-node__content {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.el-tree-node__label {
|
||||
font-size: .78rem;
|
||||
color: var(--text-color-muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
|
||||
import Viewer from '@/components/fragment/Viewer.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import { getMenus, nameize } from '@/assets/js/menu';
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
}
|
||||
})
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
const tableName = computed(() => {
|
||||
return 'roles'
|
||||
})
|
||||
|
||||
|
||||
|
||||
const treeElement = ref()
|
||||
|
||||
const treeOptions = computed(() => {
|
||||
return {
|
||||
class: (data, node) => {
|
||||
if (data.isPermission) {
|
||||
return 'permission-node'
|
||||
}
|
||||
return ''
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const gridviewActions = computed(() => {
|
||||
return [
|
||||
'edit',
|
||||
'delete',
|
||||
];
|
||||
})
|
||||
|
||||
const permissions = computed(() => {
|
||||
let permissions = buildMenuTree(getMenus())
|
||||
return [
|
||||
{
|
||||
id: 'all',
|
||||
label: '全部',
|
||||
isPermission: false,
|
||||
children: permissions,
|
||||
}
|
||||
];
|
||||
})
|
||||
|
||||
const getCheckedKeys = (model) => {
|
||||
if (!model.permissions) {
|
||||
return []
|
||||
} else {
|
||||
return JSON.parse(model.permissions)
|
||||
}
|
||||
}
|
||||
|
||||
const handleTreeChange = (model) => {
|
||||
let permissions = treeElement.value.getCheckedKeys() || [];
|
||||
model.permissions = JSON.stringify(permissions);
|
||||
}
|
||||
|
||||
const buildPermissionTree = (key, data) => {
|
||||
let permissions = []
|
||||
for (let i in data) {
|
||||
let row = data[i];
|
||||
let item = {
|
||||
id: key + "." + row.value,
|
||||
label: row.label,
|
||||
isPermission: true,
|
||||
}
|
||||
permissions.push(item)
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
||||
const buildMenuTree = (data) => {
|
||||
let values = []
|
||||
for (let i in data) {
|
||||
let row = Object.assign({}, data[i]);
|
||||
let item = {
|
||||
id: nameize(row),
|
||||
label: row.label,
|
||||
isPermission: false,
|
||||
children: []
|
||||
}
|
||||
if (row.hidden && !Array.isArray(row.permissions)) {
|
||||
continue
|
||||
}
|
||||
if (Array.isArray(row.children)) {
|
||||
item.children = buildMenuTree(row.children)
|
||||
}
|
||||
if (Array.isArray(row.permissions)) {
|
||||
let childrens = buildPermissionTree(item.id, row.permissions)
|
||||
for (let child of childrens) {
|
||||
item.children.push(child)
|
||||
}
|
||||
}
|
||||
values.push(item)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<viewer :title="title" :module-name="moduleName" :table-name="tableName" :disable-toolbar="false"
|
||||
defaultSortable="uid"></viewer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Viewer from '@/components/fragment/Viewer.vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
}
|
||||
})
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return ''
|
||||
})
|
||||
|
||||
const tableName = computed(() => {
|
||||
return 'users'
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,61 @@
|
|||
<template>
|
||||
<viewer :title="title" :module-name="moduleName" :table-name="tableName" :disable-toolbar="false"
|
||||
:disable-permission="true" defaultSortable="-created_at" :default-query="defaultQuery" :readonly="true"
|
||||
:toolbar-actions="toolbarActions">
|
||||
<template #searchform="{ model, schema }">
|
||||
<user-panel v-if="schema.column == 'receiver'" v-model="model['receiver']"></user-panel>
|
||||
<user-panel v-if="schema.column == 'sender'" v-model="model['sender']"></user-panel>
|
||||
</template>
|
||||
</viewer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Viewer from '@/components/fragment/Viewer.vue';
|
||||
import { computed } from 'vue';
|
||||
import { readUserMsg } from '@/apis/organize'
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { getModelValue } from '@/components/fragment/libs/model';
|
||||
import UserPanel from '@/components/widgets/UserPanel.vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
}
|
||||
})
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return 'organize'
|
||||
})
|
||||
|
||||
const tableName = computed(() => {
|
||||
return 'user_notices'
|
||||
})
|
||||
|
||||
const defaultQuery = computed(() => {
|
||||
return {
|
||||
created_at: [dayjs().startOf('day').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('day').format('YYYY-MM-DD HH:mm:ss')].join('/')
|
||||
}
|
||||
})
|
||||
|
||||
const toolbarActions = computed(() => {
|
||||
return ['export', 'batchDelete', {
|
||||
name: 'read',
|
||||
label: '设置已读',
|
||||
selection: true,
|
||||
callback: (values) => {
|
||||
let ps = [];
|
||||
for (let i in values) {
|
||||
let id = getModelValue(values[i], 'id');
|
||||
ps.push(readUserMsg(id))
|
||||
}
|
||||
Promise.all(ps).then(res => {
|
||||
ElMessage.success('操作成功');
|
||||
}).catch(e => {
|
||||
ElMessage.error(e.message);
|
||||
})
|
||||
}
|
||||
}]
|
||||
})
|
||||
|
||||
</script>
|
|
@ -0,0 +1,195 @@
|
|||
<template>
|
||||
<div class="profile-container">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="6">
|
||||
<div class="profile">
|
||||
<div class="profile-avatar text-center d-flex">
|
||||
<div class="flex-shrink">
|
||||
<el-avatar :size="72" :src="avatar" />
|
||||
</div>
|
||||
<div class="flex-fill">
|
||||
<h3>{{ username }}</h3>
|
||||
<span class="text-muted">{{ description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="profile-fields">
|
||||
<preview label="工号" :value="uid"></preview>
|
||||
<preview label="名称" :value="username"></preview>
|
||||
<preview label="邮箱" :value="email"></preview>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="18">
|
||||
<div class="profile-tabs">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="基本资料" name="basic">
|
||||
<el-form ref="basicForm" :model="basicModel" status-icon label-width="120px" class="segment-form">
|
||||
<el-form-item label="用户名" prop="oldPassword">
|
||||
<el-input v-model="basicModel.username" autocomplete="off" placeholder="请输入用户名" />
|
||||
</el-form-item>
|
||||
<el-form-item label="邮箱" prop="newPassword">
|
||||
<el-input v-model="basicModel.email" autocomplete="off" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item label="个人简介" prop="confirmPassword">
|
||||
<el-input v-model="basicModel.description" type="textarea" autocomplete="off" rows="10"
|
||||
placeholder="请介绍下自己" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleUpdateProfile">修改</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="密码修改" name="password">
|
||||
<el-form ref="passwordForm" :model="passwordModel" :rules="passwordRules" status-icon
|
||||
label-width="120px" class="segment-form">
|
||||
<el-form-item label="旧密码" prop="oldPassword">
|
||||
<el-input v-model="passwordModel.oldPassword" type="password" autocomplete="off"
|
||||
placeholder="请输入旧密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input v-model="passwordModel.newPassword" type="password" autocomplete="off"
|
||||
placeholder="请输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="confirmPassword">
|
||||
<el-input v-model="passwordModel.confirmPassword" type="password" autocomplete="off"
|
||||
placeholder="请再次输入新密码" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleResetPassword">修改</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.profile {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.profile-tabs{
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.profile-container {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
|
||||
.profile-avatar {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.el-avatar {
|
||||
box-shadow: var(--el-box-shadow);
|
||||
margin-right: 1.2rem;
|
||||
}
|
||||
|
||||
.el-avatar {
|
||||
margin-bottom: 1.8rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: .5rem 0 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-desc {
|
||||
h4 {
|
||||
margin: .5rem 0;
|
||||
}
|
||||
|
||||
p {
|
||||
text-indent: 2rem;
|
||||
font-size: .78rem;
|
||||
line-height: 1.48rem;
|
||||
}
|
||||
|
||||
margin-bottom: 2.8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import useUserStore from '@/stores/user'
|
||||
import Preview from '@/components/widgets/Preview.vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { updateProfile, resetPassword } from '@/apis/organize'
|
||||
import { ElMessage } from 'element-plus';
|
||||
const userStore = useUserStore()
|
||||
|
||||
const { avatar, username, email, uid, description } = storeToRefs(userStore)
|
||||
|
||||
const activeTab = ref('basic')
|
||||
|
||||
|
||||
const basicModel = ref({
|
||||
username: username,
|
||||
email: email,
|
||||
description: description,
|
||||
})
|
||||
|
||||
const passwordForm = ref()
|
||||
|
||||
const passwordModel = ref({
|
||||
oldPassword: '',
|
||||
newPassword: '',
|
||||
confirmPassword: ''
|
||||
})
|
||||
|
||||
const validateConfirmPassword = (rule, value, callback) => {
|
||||
|
||||
if (value != passwordModel.value.newPassword) {
|
||||
callback(new Error("两次密码输入不一致"))
|
||||
} else if (!/(?=.*[0-9])(?=.*[a-zA-Z]).{6,20}/.test(value)) {
|
||||
callback(new Error("密码必须包含数字和字母, 长度6-20位"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const passwordRules = ref({
|
||||
oldPassword: [
|
||||
{ required: true, message: '密码不能为空', trigger: 'blur' }
|
||||
],
|
||||
newPassword: [
|
||||
{ required: true, message: '密码不能为空', trigger: 'blur' }
|
||||
],
|
||||
confirmPassword: [
|
||||
{ validator: validateConfirmPassword, trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
const handleUpdateProfile = () => {
|
||||
updateProfile(basicModel.value).then(res => {
|
||||
|
||||
}).catch(e => {
|
||||
ElMessage.error(e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const handleResetPassword = () => {
|
||||
passwordForm.value.validate().then(valid => {
|
||||
if (valid) {
|
||||
resetPassword(passwordModel.value.oldPassword, passwordModel.value.newPassword).then(res => {
|
||||
ElMessage.success('密码重置成功')
|
||||
}).catch(e => {
|
||||
ElMessage.error(e.message)
|
||||
})
|
||||
}
|
||||
}).catch(e => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue