add admin users
This commit is contained in:
parent
b49c431a8e
commit
46ee4ddd6f
92
api.go
92
api.go
|
@ -1,8 +1,10 @@
|
||||||
package moto
|
package moto
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"git.nobla.cn/golang/kos"
|
"git.nobla.cn/golang/kos"
|
||||||
"git.nobla.cn/golang/kos/entry/http"
|
"git.nobla.cn/golang/kos/entry/http"
|
||||||
|
"git.nobla.cn/golang/kos/util/arrays"
|
||||||
"git.nobla.cn/golang/moto/common/db"
|
"git.nobla.cn/golang/moto/common/db"
|
||||||
"git.nobla.cn/golang/moto/internal/organize"
|
"git.nobla.cn/golang/moto/internal/organize"
|
||||||
"git.nobla.cn/golang/moto/internal/organize/passport"
|
"git.nobla.cn/golang/moto/internal/organize/passport"
|
||||||
|
@ -11,9 +13,20 @@ import (
|
||||||
"git.nobla.cn/golang/rest"
|
"git.nobla.cn/golang/rest"
|
||||||
restTypes "git.nobla.cn/golang/rest/types"
|
restTypes "git.nobla.cn/golang/rest/types"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
httpkg "net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed web/release
|
||||||
|
var webDir embed.FS
|
||||||
|
|
||||||
|
type (
|
||||||
|
resetPasswordRequest struct {
|
||||||
|
OldPassword string `json:"old_password"`
|
||||||
|
NewPassword string `json:"new_password"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func (svr *Server) handleLogin(ctx *http.Context) (err error) {
|
func (svr *Server) handleLogin(ctx *http.Context) (err error) {
|
||||||
var (
|
var (
|
||||||
tk *types.Tokenize
|
tk *types.Tokenize
|
||||||
|
@ -26,9 +39,27 @@ func (svr *Server) handleLogin(ctx *http.Context) (err error) {
|
||||||
if tk, err = passport.Login(ctx.Context(), req); err != nil {
|
if tk, err = passport.Login(ctx.Context(), req); err != nil {
|
||||||
return ctx.Error(http.ErrPermissionDenied, err.Error())
|
return ctx.Error(http.ErrPermissionDenied, err.Error())
|
||||||
}
|
}
|
||||||
|
ctx.SetCookie(&httpkg.Cookie{Name: organize.CookieName, Value: tk.Token, Path: "/"})
|
||||||
return ctx.Success(tk)
|
return ctx.Success(tk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleResetPassword(ctx *http.Context) (err error) {
|
||||||
|
req := &resetPasswordRequest{}
|
||||||
|
if err = ctx.Bind(req); err != nil {
|
||||||
|
return ctx.Error(http.ErrInvalidPayload, err.Error())
|
||||||
|
}
|
||||||
|
userid := ctx.User().ID
|
||||||
|
if err = passport.ResetPassword(
|
||||||
|
ctx.Request().Context(),
|
||||||
|
userid,
|
||||||
|
req.OldPassword,
|
||||||
|
req.NewPassword,
|
||||||
|
); err != nil {
|
||||||
|
return ctx.Error(http.ErrResourceUpdate, err.Error())
|
||||||
|
}
|
||||||
|
return ctx.Success("OK")
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Server) handleLogout(ctx *http.Context) (err error) {
|
func (svr *Server) handleLogout(ctx *http.Context) (err error) {
|
||||||
passport.Logout(ctx.Context(), ctx.User().Get("token"))
|
passport.Logout(ctx.Context(), ctx.User().Get("token"))
|
||||||
return ctx.Success("logout")
|
return ctx.Success("logout")
|
||||||
|
@ -41,9 +72,27 @@ func (svr *Server) handleProfile(ctx *http.Context) (err error) {
|
||||||
if profile, err = organize.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.Error(http.ErrTemporaryUnavailable, err.Error())
|
||||||
}
|
}
|
||||||
|
if arrays.Exists(ctx.User().ID, svr.cfg.AdminUsers) {
|
||||||
|
profile.Admin = true
|
||||||
|
}
|
||||||
return ctx.Success(profile)
|
return ctx.Success(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleUpdateProfile(ctx *http.Context) (err error) {
|
||||||
|
var (
|
||||||
|
profile *types.Profile
|
||||||
|
)
|
||||||
|
profile = &types.Profile{}
|
||||||
|
if err = ctx.Bind(profile); err != nil {
|
||||||
|
return ctx.Error(http.ErrInvalidPayload, err.Error())
|
||||||
|
}
|
||||||
|
if err = organize.UpdateProfile(ctx.Context(), ctx.User().ID, profile); err == nil {
|
||||||
|
return ctx.Success(profile)
|
||||||
|
} else {
|
||||||
|
return ctx.Error(http.ErrTemporaryUnavailable, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Server) handleGetConfigure(ctx *http.Context) (err error) {
|
func (svr *Server) handleGetConfigure(ctx *http.Context) (err error) {
|
||||||
return ctx.Success(map[string]string{})
|
return ctx.Success(map[string]string{})
|
||||||
}
|
}
|
||||||
|
@ -57,16 +106,16 @@ func (svr *Server) handleListSchema(ctx *http.Context) (err error) {
|
||||||
schemas, err = rest.GetSchemas(
|
schemas, err = rest.GetSchemas(
|
||||||
ctx.Request().Context(),
|
ctx.Request().Context(),
|
||||||
db.WithContext(ctx.Request().Context()),
|
db.WithContext(ctx.Request().Context()),
|
||||||
ctx.User().Get("domain"),
|
"",
|
||||||
version.ProductName,
|
version.ModuleName,
|
||||||
ctx.Param("table"),
|
ctx.Param("table"),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
schemas, err = rest.VisibleSchemas(
|
schemas, err = rest.VisibleSchemas(
|
||||||
ctx.Request().Context(),
|
ctx.Request().Context(),
|
||||||
db.WithContext(ctx.Request().Context()),
|
db.WithContext(ctx.Request().Context()),
|
||||||
ctx.User().Get("domain"),
|
"",
|
||||||
version.ProductName,
|
version.ModuleName,
|
||||||
ctx.Param("table"),
|
ctx.Param("table"),
|
||||||
scenario,
|
scenario,
|
||||||
)
|
)
|
||||||
|
@ -116,17 +165,48 @@ func (svr *Server) handleDeleteSchema(ctx *http.Context) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleDepartmentTypes(ctx *http.Context) (err error) {
|
||||||
|
return ctx.Success(organize.DepartmentTypes(ctx.Context()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleRoleTypes(ctx *http.Context) (err error) {
|
||||||
|
return ctx.Success(organize.RoleTypes(ctx.Context()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleUserTypes(ctx *http.Context) (err error) {
|
||||||
|
return ctx.Success(organize.UserTypes(ctx.Context()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleUserTags(ctx *http.Context) (err error) {
|
||||||
|
return ctx.Success(organize.UserTags(ctx.Context()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svr *Server) handleUserAvatar(ctx *http.Context) (err error) {
|
||||||
|
organize.Avatar(ctx.Context(), svr.cfg.Avatar.Dirname, ctx.Param("id"), ctx.Request(), ctx.Response())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (svr *Server) routes() {
|
func (svr *Server) routes() {
|
||||||
|
|
||||||
kos.Http().Use(organize.AuthMiddleware)
|
kos.Http().Use(organize.AuthMiddleware)
|
||||||
organize.AllowUri("/passport/login")
|
organize.AllowUri("/passport/login")
|
||||||
|
|
||||||
|
kos.Http().Root("/web/release", httpkg.FS(webDir))
|
||||||
|
|
||||||
kos.Http().Handle(http.MethodPost, "/passport/login", svr.handleLogin)
|
kos.Http().Handle(http.MethodPost, "/passport/login", svr.handleLogin)
|
||||||
|
kos.Http().Handle(http.MethodPut, "/passport/reset-password", svr.handleResetPassword)
|
||||||
kos.Http().Handle(http.MethodDelete, "/passport/logout", svr.handleLogout)
|
kos.Http().Handle(http.MethodDelete, "/passport/logout", svr.handleLogout)
|
||||||
kos.Http().Handle(http.MethodGet, "/user/profile", svr.handleProfile)
|
kos.Http().Handle(http.MethodGet, "/user/profile", svr.handleProfile)
|
||||||
|
kos.Http().Handle(http.MethodPut, "/user/profile", svr.handleUpdateProfile)
|
||||||
kos.Http().Handle(http.MethodGet, "/user/configures", svr.handleGetConfigure)
|
kos.Http().Handle(http.MethodGet, "/user/configures", svr.handleGetConfigure)
|
||||||
|
|
||||||
kos.Http().Handle(http.MethodGet, "/rest/schema/:module/:table", svr.handleListSchema)
|
kos.Http().Handle(http.MethodGet, "/rest/schema/:table", svr.handleListSchema)
|
||||||
kos.Http().Handle(http.MethodPut, "/rest/schema/:module/:table", svr.handleSaveSchema)
|
kos.Http().Handle(http.MethodPut, "/rest/schema/:table", svr.handleSaveSchema)
|
||||||
kos.Http().Handle(http.MethodDelete, "/rest/schema/:id", svr.handleDeleteSchema)
|
kos.Http().Handle(http.MethodDelete, "/rest/schema/:id", svr.handleDeleteSchema)
|
||||||
|
|
||||||
|
kos.Http().Handle(http.MethodGet, "/organize/department-types", svr.handleDepartmentTypes)
|
||||||
|
kos.Http().Handle(http.MethodGet, "/organize/role-types", svr.handleRoleTypes)
|
||||||
|
kos.Http().Handle(http.MethodGet, "/organize/user-types", svr.handleUserTypes)
|
||||||
|
kos.Http().Handle(http.MethodGet, "/organize/user-tags", svr.handleUserTags)
|
||||||
|
kos.Http().Handle(http.MethodGet, "/profile/avatar/:id", svr.handleUserAvatar)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,13 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"git.nobla.cn/golang/kos"
|
||||||
"git.nobla.cn/golang/moto/config"
|
"git.nobla.cn/golang/moto/config"
|
||||||
"git.nobla.cn/golang/rest/types"
|
"git.nobla.cn/golang/rest/types"
|
||||||
"github.com/go-sql-driver/mysql"
|
"github.com/go-sql-driver/mysql"
|
||||||
lru "github.com/hashicorp/golang-lru/v2"
|
lru "github.com/hashicorp/golang-lru/v2"
|
||||||
mysqlDriver "gorm.io/driver/mysql"
|
mysqlDriver "gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"reflect"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ func TryCache(ctx context.Context, key string, f CachingFunc, cbs ...CacheOption
|
||||||
var (
|
var (
|
||||||
ok bool
|
ok bool
|
||||||
hasDependValue bool
|
hasDependValue bool
|
||||||
dependValue any
|
dependValue string
|
||||||
)
|
)
|
||||||
opts := &CacheOptions{}
|
opts := &CacheOptions{}
|
||||||
for _, cb := range cbs {
|
for _, cb := range cbs {
|
||||||
|
@ -50,7 +50,7 @@ func TryCache(ctx context.Context, key string, f CachingFunc, cbs ...CacheOption
|
||||||
//对比依赖值
|
//对比依赖值
|
||||||
if err = WithContext(ctx).Raw(opts.dependSQL, opts.dependArgs...).Scan(&dependValue).Error; err == nil {
|
if err = WithContext(ctx).Raw(opts.dependSQL, opts.dependArgs...).Scan(&dependValue).Error; err == nil {
|
||||||
hasDependValue = true
|
hasDependValue = true
|
||||||
if reflect.DeepEqual(entry.compareValue, dependValue) {
|
if entry.compareValue == dependValue {
|
||||||
entry.lastChecked = time.Now()
|
entry.lastChecked = time.Now()
|
||||||
return entry.storeValue, nil
|
return entry.storeValue, nil
|
||||||
} else {
|
} else {
|
||||||
|
@ -104,6 +104,9 @@ func Init(ctx context.Context, cfg config.Database, plugins ...gorm.Plugin) (err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db = db.WithContext(ctx)
|
db = db.WithContext(ctx)
|
||||||
|
if kos.Debug() {
|
||||||
|
db = db.Debug()
|
||||||
|
}
|
||||||
for _, plugin := range plugins {
|
for _, plugin := range plugins {
|
||||||
if err = db.Use(plugin); err != nil {
|
if err = db.Use(plugin); err != nil {
|
||||||
return
|
return
|
||||||
|
|
|
@ -17,7 +17,7 @@ type (
|
||||||
|
|
||||||
cacheEntry struct {
|
cacheEntry struct {
|
||||||
storeValue any
|
storeValue any
|
||||||
compareValue any
|
compareValue string
|
||||||
createdAt time.Time
|
createdAt time.Time
|
||||||
lastChecked time.Time
|
lastChecked time.Time
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
Address string `json:"address" yaml:"address"`
|
Address string `json:"address" yaml:"address"`
|
||||||
Username string `json:"username" yaml:"username"`
|
Username string `json:"username" yaml:"username"`
|
||||||
|
@ -7,11 +9,18 @@ type Database struct {
|
||||||
Database string `json:"database" yaml:"database"`
|
Database string `json:"database" yaml:"database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Avatar struct {
|
||||||
|
Dirname string `json:"dirname" yaml:"dirname"`
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Database Database `json:"database" yaml:"database"`
|
Database Database `json:"database" yaml:"database"`
|
||||||
|
Avatar Avatar `json:"avatar" yaml:"avatar"`
|
||||||
|
AdminUsers []string `json:"admin_users" yaml:"adminUsers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Config {
|
func New() *Config {
|
||||||
cfg := &Config{}
|
cfg := &Config{}
|
||||||
|
cfg.Avatar.Dirname = os.TempDir()
|
||||||
return cfg
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,4 +2,7 @@ database:
|
||||||
address: "192.168.9.199:3306"
|
address: "192.168.9.199:3306"
|
||||||
username: "root"
|
username: "root"
|
||||||
password: "root"
|
password: "root"
|
||||||
database: "test2"
|
database: "test2"
|
||||||
|
|
||||||
|
adminUsers:
|
||||||
|
- "1000"
|
7
go.mod
7
go.mod
|
@ -4,7 +4,8 @@ go 1.22.9
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.nobla.cn/golang/kos v0.1.32
|
git.nobla.cn/golang/kos v0.1.32
|
||||||
git.nobla.cn/golang/rest v0.0.2
|
git.nobla.cn/golang/rest v0.0.4
|
||||||
|
github.com/disintegration/letteravatar v0.0.0-20160912210445-1a457b860450
|
||||||
github.com/go-sql-driver/mysql v1.8.1
|
github.com/go-sql-driver/mysql v1.8.1
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
|
@ -20,6 +21,7 @@ require (
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
|
@ -30,7 +32,8 @@ require (
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
github.com/rs/xid v1.6.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.19.0 // indirect
|
golang.org/x/crypto v0.19.0 // indirect
|
||||||
|
golang.org/x/image v0.23.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/sys v0.17.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
14
go.sum
14
go.sum
|
@ -2,11 +2,13 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
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 h1:sFVCA7vKc8dPUd0cxzwExOSPX2mmMh2IuwL6cYS1pBc=
|
||||||
git.nobla.cn/golang/kos v0.1.32/go.mod h1:35Z070+5oB39WcVrh5DDlnVeftL/Ccmscw2MZFe9fUg=
|
git.nobla.cn/golang/kos v0.1.32/go.mod h1:35Z070+5oB39WcVrh5DDlnVeftL/Ccmscw2MZFe9fUg=
|
||||||
git.nobla.cn/golang/rest v0.0.2 h1:b5hmGuVb1zXFQ8O2AYCi8628JGftgH2TL5BLftCN1OU=
|
git.nobla.cn/golang/rest v0.0.4 h1:i8hD/z5UAgoKjRA0ED83yK0q0IuYJ+xiiUZ1nGdn8PY=
|
||||||
git.nobla.cn/golang/rest v0.0.2/go.mod h1:tGDOul2GGJtxk6fAeu+YLpMt/Up/TsBonTkygymN/wE=
|
git.nobla.cn/golang/rest v0.0.4/go.mod h1:tGDOul2GGJtxk6fAeu+YLpMt/Up/TsBonTkygymN/wE=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/disintegration/letteravatar v0.0.0-20160912210445-1a457b860450 h1:bONK4hIXo1mYT3u9dtW7mH7xsy6PwzwxW//IxAlPrbI=
|
||||||
|
github.com/disintegration/letteravatar v0.0.0-20160912210445-1a457b860450/go.mod h1:/xRvTkTQVbtTtCoVg1yFA4/mrCtaqEw5SaHacBbcT5I=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
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/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 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
@ -20,6 +22,8 @@ github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC
|
||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
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 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
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/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 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
@ -54,13 +58,15 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
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 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||||
|
golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68=
|
||||||
|
golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
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/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.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 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
package organize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.nobla.cn/golang/moto/common/db"
|
||||||
|
"git.nobla.cn/golang/moto/models"
|
||||||
|
"github.com/disintegration/letteravatar"
|
||||||
|
"image"
|
||||||
|
"image/png"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Avatar(ctx context.Context, dirname, userid string, r *http.Request, w http.ResponseWriter) {
|
||||||
|
var (
|
||||||
|
fs os.FileInfo
|
||||||
|
fp *os.File
|
||||||
|
err error
|
||||||
|
img image.Image
|
||||||
|
modTime time.Time
|
||||||
|
)
|
||||||
|
tx := db.DB().WithContext(ctx)
|
||||||
|
extName := ".png"
|
||||||
|
if dirname != "" {
|
||||||
|
filename := path.Join(dirname, userid+extName)
|
||||||
|
if fp, err = os.Open(filename); err == nil {
|
||||||
|
defer fp.Close()
|
||||||
|
if fs, err = os.Stat(filename); err == nil {
|
||||||
|
modTime = fs.ModTime()
|
||||||
|
}
|
||||||
|
http.ServeContent(w, r, userid+extName, modTime, fp)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userModel := &models.User{}
|
||||||
|
if err = tx.Where("uid=?", userid).First(userModel).Error; err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
firstLetter, _ := utf8.DecodeRuneInString(strings.ToUpper(userModel.Username))
|
||||||
|
if img, err = letteravatar.Draw(64, firstLetter, nil); err == nil {
|
||||||
|
w.Header().Set("Content-Type", mime.TypeByExtension(extName))
|
||||||
|
if dirname != "" {
|
||||||
|
filename := path.Join(dirname, userid+extName)
|
||||||
|
if fp, err = os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644); err == nil {
|
||||||
|
err = png.Encode(fp, img)
|
||||||
|
err = fp.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = png.Encode(w, img)
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,7 +47,7 @@ func RecursiveNestedDepartment(ctx context.Context, parent string, level int, de
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func DepartmentUserNested(ctx context.Context, domainName string) []*types.NestedValue {
|
func DepartmentUserNested(ctx context.Context) []*types.NestedValue {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
value any
|
value any
|
||||||
|
@ -55,21 +55,21 @@ func DepartmentUserNested(ctx context.Context, domainName string) []*types.Neste
|
||||||
departments []*models.Department
|
departments []*models.Department
|
||||||
)
|
)
|
||||||
|
|
||||||
if value, err = db.TryCache(ctx, fmt.Sprintf("domain:%s:departments", domainName), func(tx *gorm.DB) (any, error) {
|
if value, err = db.TryCache(ctx, fmt.Sprintf("departments"), func(tx *gorm.DB) (any, error) {
|
||||||
departments = make([]*models.Department, 0)
|
departments = make([]*models.Department, 0)
|
||||||
err = tx.Where("domain=?", domainName).Order("created_at ASC").Find(&departments).Error
|
err = tx.Order("created_at ASC").Find(&departments).Error
|
||||||
return departments, err
|
return departments, err
|
||||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `departments` WHERE `domain`=?", domainName)); err == nil {
|
}, db.WithDepend("SELECT max(`updated_at`) FROM `departments`")); err == nil {
|
||||||
departments = value.([]*models.Department)
|
departments = value.([]*models.Department)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if value, err = db.TryCache(ctx, fmt.Sprintf("domain:%s:users", domainName), func(tx *gorm.DB) (any, error) {
|
if value, err = db.TryCache(ctx, fmt.Sprintf("users"), func(tx *gorm.DB) (any, error) {
|
||||||
users = make([]*models.User, 0)
|
users = make([]*models.User, 0)
|
||||||
err = tx.Where("domain=?", domainName).Order("uid ASC").Find(&users).Error
|
err = tx.Order("uid ASC").Find(&users).Error
|
||||||
return users, err
|
return users, err
|
||||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `users` WHERE `domain`=?", domainName)); err == nil {
|
}, db.WithDepend("SELECT max(`updated_at`) FROM `users`")); err == nil {
|
||||||
users = value.([]*models.User)
|
users = value.([]*models.User)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
|
@ -96,15 +96,15 @@ func DepartmentUserNested(ctx context.Context, domainName string) []*types.Neste
|
||||||
return values
|
return values
|
||||||
}
|
}
|
||||||
|
|
||||||
func DepartmentTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
func DepartmentTypes(ctx context.Context) []*types.TypeValue {
|
||||||
result, err := db.TryCache(ctx, fmt.Sprintf("domain:%s:department:types", domainName), func(tx *gorm.DB) (any, error) {
|
result, err := db.TryCache(ctx, fmt.Sprintf("department:types"), func(tx *gorm.DB) (any, error) {
|
||||||
values := make([]*models.Department, 0)
|
values := make([]*models.Department, 0)
|
||||||
if err := db.WithContext(ctx).Where("domain=?", domainName).Find(&values).Error; err == nil {
|
if err := db.WithContext(ctx).Find(&values).Error; err == nil {
|
||||||
return RecursiveDepartment(ctx, "", 0, values), nil
|
return RecursiveDepartment(ctx, "", 0, values), nil
|
||||||
} else {
|
} else {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `departments` WHERE `domain`=?", domainName))
|
}, db.WithDepend("SELECT max(`updated_at`) FROM `departments`"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result.([]*types.TypeValue)
|
return result.([]*types.TypeValue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package passport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.nobla.cn/golang/moto/common/db"
|
||||||
|
"git.nobla.cn/golang/moto/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ResetPassword(ctx context.Context, userid string, oldPassword, newPassword string) (err error) {
|
||||||
|
tx := db.WithContext(ctx)
|
||||||
|
userModel := &models.User{}
|
||||||
|
if err = tx.Where("uid=?", userid).First(userModel).Error; err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userModel.Password == oldPassword || md5Hash(oldPassword) == userModel.Password {
|
||||||
|
err = tx.Where("uid=?", userid).Model(&models.User{}).UpdateColumn("password", md5Hash(newPassword)).Error
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("invalid password")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"git.nobla.cn/golang/moto/common/db"
|
"git.nobla.cn/golang/moto/common/db"
|
||||||
"git.nobla.cn/golang/moto/internal/organize/types"
|
"git.nobla.cn/golang/moto/internal/organize/types"
|
||||||
|
"git.nobla.cn/golang/moto/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Profile(ctx context.Context, uid string) (profile *types.Profile, err error) {
|
func Profile(ctx context.Context, uid string) (profile *types.Profile, err error) {
|
||||||
|
@ -15,3 +16,25 @@ func Profile(ctx context.Context, uid string) (profile *types.Profile, err error
|
||||||
First(profile).Error
|
First(profile).Error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateProfile(ctx context.Context, uid string, model *types.Profile) (err error) {
|
||||||
|
tx := db.DB().WithContext(ctx)
|
||||||
|
userModel := &models.User{}
|
||||||
|
if err = tx.Where("uid=?", uid).First(userModel).Error; err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if model.Avatar != "" {
|
||||||
|
userModel.Avatar = model.Avatar
|
||||||
|
}
|
||||||
|
if model.Email != "" {
|
||||||
|
userModel.Email = model.Email
|
||||||
|
}
|
||||||
|
if model.Username != "" {
|
||||||
|
userModel.Username = model.Username
|
||||||
|
}
|
||||||
|
if model.Description != "" {
|
||||||
|
userModel.Description = model.Description
|
||||||
|
}
|
||||||
|
err = tx.Save(userModel).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RoleTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
func RoleTypes(ctx context.Context) []*types.TypeValue {
|
||||||
result, err := db.TryCache(ctx, fmt.Sprintf("domain:%s:role:types", domainName), func(tx *gorm.DB) (any, error) {
|
result, err := db.TryCache(ctx, fmt.Sprintf("role:types"), func(tx *gorm.DB) (any, error) {
|
||||||
return rest.ModelTypes(ctx, tx, &models.Role{}, domainName, "name", "id"), nil
|
return rest.ModelTypes(ctx, tx, &models.Role{}, "", "name", "id"), nil
|
||||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `roles` WHERE `domain`=?", domainName))
|
}, db.WithDepend("SELECT max(`updated_at`) FROM `roles`"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result.([]*types.TypeValue)
|
return result.([]*types.TypeValue)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ type (
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
|
Admin bool `json:"admin"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Permissions string `json:"permissions"`
|
Permissions string `json:"permissions"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,10 +9,10 @@ import (
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func UserTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
func UserTypes(ctx context.Context) []*types.TypeValue {
|
||||||
result, err := db.TryCache(ctx, fmt.Sprintf("domain:%s:user:types", domainName), func(tx *gorm.DB) (any, error) {
|
result, err := db.TryCache(ctx, fmt.Sprintf("user:types"), func(tx *gorm.DB) (any, error) {
|
||||||
values := make([]*models.User, 0)
|
values := make([]*models.User, 0)
|
||||||
err := tx.Where("domain=?", domainName).Order("uid ASC").Find(&values).Error
|
err := tx.Order("uid ASC").Find(&values).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,34 @@ func UserTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return data, nil
|
return data, nil
|
||||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `users` WHERE `domain`=?", domainName))
|
}, db.WithDepend("SELECT max(`updated_at`) FROM `users`"))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return result.([]*types.TypeValue)
|
return result.([]*types.TypeValue)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UserTags(ctx context.Context) []*types.TypeValue {
|
||||||
|
result, err := db.TryCache(ctx, fmt.Sprintf("users:tags"), func(tx *gorm.DB) (any, error) {
|
||||||
|
values := make([]*models.User, 0)
|
||||||
|
if errTx := tx.Select("DISTINCT(`tag`) AS `tag`").Find(&values).Error; errTx == nil {
|
||||||
|
vs := make([]*types.TypeValue, 0, len(values))
|
||||||
|
for _, row := range values {
|
||||||
|
if row.Tag != "" {
|
||||||
|
vs = append(vs, &types.TypeValue{
|
||||||
|
Label: row.Tag,
|
||||||
|
Value: row.Tag,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs, nil
|
||||||
|
} else {
|
||||||
|
return nil, errTx
|
||||||
|
}
|
||||||
|
}, db.WithDepend("SELECT max(`updated_at`) FROM `users`"))
|
||||||
|
if err == nil {
|
||||||
|
return result.([]*types.TypeValue)
|
||||||
|
} else {
|
||||||
|
return []*types.TypeValue{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package moto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.nobla.cn/golang/moto/common/db"
|
||||||
|
"git.nobla.cn/golang/moto/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultUsers = map[string]string{
|
||||||
|
"1000": "admin",
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDepartments = map[string]string{
|
||||||
|
"admin": "客服部",
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRoles = map[string]string{
|
||||||
|
"admin": "管理员",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func migrate(ctx context.Context) (err error) {
|
||||||
|
var (
|
||||||
|
count int64
|
||||||
|
deptID string
|
||||||
|
roleID string
|
||||||
|
)
|
||||||
|
tx := db.DB().WithContext(ctx)
|
||||||
|
tx.Model(&models.Department{}).Count(&count)
|
||||||
|
if count <= 0 {
|
||||||
|
for _, v := range defaultDepartments {
|
||||||
|
deptModel := &models.Department{
|
||||||
|
Parent: "",
|
||||||
|
Name: v,
|
||||||
|
}
|
||||||
|
if err = tx.Save(deptModel).Error; err == nil {
|
||||||
|
if deptID == "" {
|
||||||
|
deptID = deptModel.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Model(&models.Role{}).Count(&count)
|
||||||
|
if count <= 0 {
|
||||||
|
for _, v := range defaultRoles {
|
||||||
|
roleModel := &models.Role{
|
||||||
|
Name: v,
|
||||||
|
Permissions: "[]",
|
||||||
|
}
|
||||||
|
if err = tx.Save(roleModel).Error; err == nil {
|
||||||
|
if roleID == "" {
|
||||||
|
roleID = roleModel.ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.Model(&models.User{}).Count(&count)
|
||||||
|
if count <= 0 {
|
||||||
|
for k, v := range defaultUsers {
|
||||||
|
tx.Save(&models.User{
|
||||||
|
UID: k,
|
||||||
|
Department: deptID,
|
||||||
|
Role: roleID,
|
||||||
|
Username: v,
|
||||||
|
Password: "Passwd#" + k,
|
||||||
|
Email: k + "@echo.me",
|
||||||
|
Avatar: "/profile/avatar/" + k,
|
||||||
|
Gender: "man",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"git.nobla.cn/golang/moto/common/db"
|
"git.nobla.cn/golang/moto/common/db"
|
||||||
"git.nobla.cn/golang/moto/config"
|
"git.nobla.cn/golang/moto/config"
|
||||||
"git.nobla.cn/golang/moto/models"
|
"git.nobla.cn/golang/moto/models"
|
||||||
|
"git.nobla.cn/golang/moto/version"
|
||||||
"git.nobla.cn/golang/rest"
|
"git.nobla.cn/golang/rest"
|
||||||
"git.nobla.cn/golang/rest/plugins/identity"
|
"git.nobla.cn/golang/rest/plugins/identity"
|
||||||
"git.nobla.cn/golang/rest/plugins/validate"
|
"git.nobla.cn/golang/rest/plugins/validate"
|
||||||
|
@ -35,10 +36,13 @@ func (svr *Server) prepare() (err error) {
|
||||||
if err = db.DB().AutoMigrate(item); err != nil {
|
if err = db.DB().AutoMigrate(item); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = rest.AutoMigrate(svr.ctx, db.DB(), item, rest.WithoutDomain(), rest.WithModuleName("rest")); err != nil {
|
if err = rest.AutoMigrate(svr.ctx, db.DB(), item, rest.WithoutDomain(), rest.WithModuleName(version.ModuleName)); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err = migrate(svr.ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,4 +3,5 @@ package version
|
||||||
var (
|
var (
|
||||||
ProductName = "moto"
|
ProductName = "moto"
|
||||||
Version = "0.0.1"
|
Version = "0.0.1"
|
||||||
|
ModuleName = "rest"
|
||||||
)
|
)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>MOTO</title>
|
||||||
|
<link rel="stylesheet" href="//at.alicdn.com/t/c/font_3832926_ya7vh14nje.css">
|
||||||
|
<script type="module" crossorigin src="/static/index-BGRRVEuU.js"></script>
|
||||||
|
<link rel="stylesheet" crossorigin href="/static/index-BhBbL-Pz.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -30,11 +30,11 @@ export function userLogout() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateProfile(data) {
|
export function updateProfile(data) {
|
||||||
return request.put('/organize/profile', data)
|
return request.put('/user/profile', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetPassword(oldPwd, newPwd) {
|
export function resetPassword(oldPwd, newPwd) {
|
||||||
return request.put('/organize/password', {
|
return request.put('/passport/reset-password', {
|
||||||
old_password: oldPwd,
|
old_password: oldPwd,
|
||||||
new_password: newPwd
|
new_password: newPwd
|
||||||
})
|
})
|
||||||
|
@ -53,22 +53,22 @@ export function getDepartmentUsers(id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUsers(id) {
|
export function getUsers(id) {
|
||||||
return request.get(`/organize/user-list`)
|
return request.get(`/organize/user-list`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClientHello() {
|
export function getClientHello() {
|
||||||
return request.post(`/organize/client-hello`)
|
return request.post(`/organize/client-hello`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserAvatar(uid) {
|
export function getUserAvatar(uid) {
|
||||||
return `${getBaseUrl()}/organize/avatar/${uid}`
|
return `${getBaseUrl()}/organize/avatar/${uid}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUserAttr(name, value) {
|
export function setUserAttr(name, value) {
|
||||||
let data = '';
|
let data = '';
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
try {
|
try {
|
||||||
data = JSON.stringify(value)
|
data = JSON.stringify(value);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
data = value
|
data = value
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,54 +25,6 @@ let statusMap = [
|
||||||
"value": "holiday",
|
"value": "holiday",
|
||||||
"color": "#b3261e",
|
"color": "#b3261e",
|
||||||
"retain": false
|
"retain": false
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "振铃",
|
|
||||||
"value": "ringing",
|
|
||||||
"color": "#e63757",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "通话",
|
|
||||||
"value": "answer",
|
|
||||||
"color": "#b3261e",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "静音",
|
|
||||||
"value": "muted",
|
|
||||||
"color": "#34AB62",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "转接",
|
|
||||||
"value": "transfer",
|
|
||||||
"color": "#C8D541",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "耳语",
|
|
||||||
"value": "whisper",
|
|
||||||
"color": "#43539D",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "监听",
|
|
||||||
"value": "eavesdrop",
|
|
||||||
"color": "#0824F8",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "未注册",
|
|
||||||
"value": "unavailable",
|
|
||||||
"color": "#bbbbbb",
|
|
||||||
"retain": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"label": "话后",
|
|
||||||
"value": "wrapUp",
|
|
||||||
"color": "#E36B09",
|
|
||||||
"retain": true
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -426,6 +426,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-select {
|
||||||
|
width: var(--form-control-width);
|
||||||
|
}
|
||||||
|
|
||||||
.el-input {
|
.el-input {
|
||||||
width: var(--form-control-width);
|
width: var(--form-control-width);
|
||||||
|
|
||||||
|
@ -520,13 +524,14 @@ a {
|
||||||
.avatar-list {
|
.avatar-list {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
.el-avatar{
|
.el-avatar {
|
||||||
margin-right: .25rem;
|
margin-right: .25rem;
|
||||||
margin-bottom: .25rem;
|
margin-bottom: .25rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.avatar-footer{
|
|
||||||
|
.avatar-footer {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
font-size: .8rem;
|
font-size: .8rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -182,13 +182,23 @@ class CRUD {
|
||||||
resolve(this, this.schemas)
|
resolve(this, this.schemas)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpclicent.get(`/rest/schema/${this.modelName}/${this.tableName}`).then(res => {
|
if (this.modelName != "") {
|
||||||
this.schemas = res;
|
httpclicent.get(`/rest/schema/${this.modelName}/${this.tableName}`).then(res => {
|
||||||
this.__prepare()
|
this.schemas = res;
|
||||||
resolve(this, this.schemas);
|
this.__prepare()
|
||||||
}).catch(e => {
|
resolve(this, this.schemas);
|
||||||
reject(e)
|
}).catch(e => {
|
||||||
})
|
reject(e)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
httpclicent.get(`/rest/schema/${this.tableName}`).then(res => {
|
||||||
|
this.schemas = res;
|
||||||
|
this.__prepare()
|
||||||
|
resolve(this, this.schemas);
|
||||||
|
}).catch(e => {
|
||||||
|
reject(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
<template>
|
|
||||||
<span class="call-widget" @click="handleOffer">
|
|
||||||
{{ value }}
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed } from 'vue';
|
|
||||||
import useUserStore from '@/stores/user'
|
|
||||||
import bus from 'vue3-eventbus'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
number: {
|
|
||||||
type: String,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const value = computed(() => {
|
|
||||||
const { hiddlePhone } = useUserStore()
|
|
||||||
if (hiddlePhone) {
|
|
||||||
return invisible(props.number)
|
|
||||||
}
|
|
||||||
return props.number
|
|
||||||
})
|
|
||||||
|
|
||||||
const invisible = (s) => {
|
|
||||||
if (typeof s !== 'string') {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if (s.length <= 5) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
var reg = /^(\d{3})\d{4}(\d+)$/;
|
|
||||||
if (s.length <= 5) {
|
|
||||||
reg = /^(\d{2})\d{2}(\d+)$/;
|
|
||||||
}
|
|
||||||
return s.replace(reg, "$1****$2");
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOffer = (e) => {
|
|
||||||
bus.emit("sip_offer", { callee: props.number })
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -68,7 +68,7 @@ const menu = [
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "座席管理",
|
label: "用户管理",
|
||||||
route: "/organize/users",
|
route: "/organize/users",
|
||||||
permissions: [
|
permissions: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,11 +12,6 @@
|
||||||
<li>
|
<li>
|
||||||
<icon name="fullscreen" @click="handleFullscreen"></icon>
|
<icon name="fullscreen" @click="handleFullscreen"></icon>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<el-badge :value="unreadMsgCount" :hidden="unreadMsgCount <= 0">
|
|
||||||
<icon name="message" @click="handleListNotices"></icon>
|
|
||||||
</el-badge>
|
|
||||||
</li>
|
|
||||||
<li style="display: none;">
|
<li style="display: none;">
|
||||||
<el-dropdown @command="handleChangeLang">
|
<el-dropdown @command="handleChangeLang">
|
||||||
<icon name="network"></icon>
|
<icon name="network"></icon>
|
||||||
|
@ -79,17 +74,17 @@ import Icon from '@/components/widgets/Icon.vue';
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { getUserStatus, getStatusText, getStatusTextColor } from '@/assets/js/status'
|
import { getUserStatus, getStatusText, getStatusTextColor } from '@/assets/js/status'
|
||||||
import { ElNotification } from 'element-plus';
|
import { ElNotification } from 'element-plus';
|
||||||
import Notice from './parts/Notice.vue';
|
|
||||||
|
|
||||||
const systemStore = useSystemStore();
|
const systemStore = useSystemStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const userState = ref('idle');
|
const userState = ref('idle');
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false);
|
||||||
|
|
||||||
const { logoUrl, productName } = storeToRefs(systemStore);
|
const { logoUrl, productName } = storeToRefs(systemStore);
|
||||||
const { headerBackgroundColor } = storeToRefs(themeStore);
|
const { headerBackgroundColor } = storeToRefs(themeStore);
|
||||||
const { uid, avatar, username, unreadMsgCount } = storeToRefs(userStore);
|
const { avatar, username } = storeToRefs(userStore);
|
||||||
|
|
||||||
|
|
||||||
const logout = inject('logout');
|
const logout = inject('logout');
|
||||||
|
@ -114,11 +109,6 @@ const handleFullscreen = (e) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleListNotices = (e) => {
|
|
||||||
let uid = userStore.uid;
|
|
||||||
router.push(`/organize/user/notice?receiver=${uid}&read=no`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleChangeLang = (e) => {
|
const handleChangeLang = (e) => {
|
||||||
systemStore.setLanguage(e)
|
systemStore.setLanguage(e)
|
||||||
}
|
}
|
||||||
|
@ -145,28 +135,7 @@ const handleMenuCommand = (e) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const refreshUnreadMsgCount = () => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const doUserStatusChange = (e) => {
|
|
||||||
if (e.data) {
|
|
||||||
userState.value = e.data.state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const doUserNotice = (e) => {
|
|
||||||
let data = e.data;
|
|
||||||
let notice = ElNotification({
|
|
||||||
title: data.title,
|
|
||||||
position: 'bottom-right',
|
|
||||||
message: h(Notice, { data: data, onDone: () => { notice.close() } })
|
|
||||||
})
|
|
||||||
refreshUnreadMsgCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
refreshUnreadMsgCount();
|
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
|
|
@ -54,7 +54,7 @@ const { productName, collapse, sidebarWidth, flowSidebarVisible } = storeToRefs(
|
||||||
|
|
||||||
const { backgroundColor, sidebarBackgroundColor } = storeToRefs(themeStore);
|
const { backgroundColor, sidebarBackgroundColor } = storeToRefs(themeStore);
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(true)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
|
@ -87,7 +87,6 @@ provide('logout', logout)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.title = productName.value
|
document.title = productName.value
|
||||||
let schema = window.location.protocol == 'http:' ? "ws://" : 'wss://'
|
|
||||||
//更新用户信息
|
//更新用户信息
|
||||||
getConfigure().then(res => {
|
getConfigure().then(res => {
|
||||||
const { setAttributeValue } = useSystemStore();
|
const { setAttributeValue } = useSystemStore();
|
||||||
|
@ -95,6 +94,14 @@ onMounted(() => {
|
||||||
let row = res[i];
|
let row = res[i];
|
||||||
setAttributeValue(row.attribute, row.value);
|
setAttributeValue(row.attribute, row.value);
|
||||||
}
|
}
|
||||||
|
return getUserProfile();
|
||||||
|
}).then(res => {
|
||||||
|
const { setUsername, setIsAdmin, setPermissions, setAvatar } = useUserStore()
|
||||||
|
setIsAdmin(res.admin);
|
||||||
|
setUsername(res.username)
|
||||||
|
setAvatar(res.avatar)
|
||||||
|
setPermissions(res.permissions)
|
||||||
|
loading.value = false
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
router.push('/login');
|
router.push('/login');
|
||||||
|
|
|
@ -1,345 +0,0 @@
|
||||||
<template>
|
|
||||||
<ul class="list list-inline">
|
|
||||||
<!--
|
|
||||||
<li class="hidden-sm-and-down">
|
|
||||||
<el-input :prefix-icon="Search" v-model="qs" clearable class="round"></el-input>
|
|
||||||
</li>
|
|
||||||
-->
|
|
||||||
<li>
|
|
||||||
<el-popover placement="bottom" :width="200" trigger="click">
|
|
||||||
<template #reference>
|
|
||||||
<icon name="phone"></icon>
|
|
||||||
</template>
|
|
||||||
<div class="dialplate" @keydown="handleKeypress">
|
|
||||||
<div class="dialplate-input">
|
|
||||||
<el-input v-model="number" placeholder="请输入要拨打的号码" readonly></el-input>
|
|
||||||
</div>
|
|
||||||
<div class="dialplate-clear">
|
|
||||||
<el-icon>
|
|
||||||
<Close v-if="number.length > 0" @click="handleClearInput"></Close>
|
|
||||||
</el-icon>
|
|
||||||
</div>
|
|
||||||
<div class="dialplate-button" @click="handleInput(item)" v-for="item in dialNumbers">{{ item }}
|
|
||||||
</div>
|
|
||||||
<div class="dialplate-actions">
|
|
||||||
<el-button type="success" circle @click="handleOffer">
|
|
||||||
<icon name="phone"></icon>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-popover>
|
|
||||||
</li>
|
|
||||||
<li v-if="inWrapUp">
|
|
||||||
<el-tooltip content="结束整理">
|
|
||||||
<icon name="wrapup" class="text-danger" @click="handleEndWraup"></icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</li>
|
|
||||||
<li v-if="inCalling">
|
|
||||||
<el-tooltip content="保持">
|
|
||||||
<icon name="muted" class="text-warning" @click="handleToggleHold"></icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</li>
|
|
||||||
<li v-if="canHanguped">
|
|
||||||
<el-tooltip content="挂机">
|
|
||||||
<icon name="phone" class="text-danger" @click="handleHangup"></icon>
|
|
||||||
</el-tooltip>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<Teleport to="body">
|
|
||||||
<popup v-model="dialogVisible" :model="call"></popup>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { computed, onMounted, onUnmounted, provide, ref } from 'vue'
|
|
||||||
import Connection from '@/assets/js/connection'
|
|
||||||
import Popup from './Popup.vue'
|
|
||||||
import Sip from '@/assets/js/sip'
|
|
||||||
import useUserStore from '@/stores/user'
|
|
||||||
import Icon from '@/components/widgets/Icon.vue'
|
|
||||||
import { Search } from '@element-plus/icons-vue'
|
|
||||||
import { nextID, hangup, endWrapUp } from '@/apis/control'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { Close } from '@element-plus/icons-vue'
|
|
||||||
import { getBaseHost } from '@/apis/request'
|
|
||||||
import bus from 'vue3-eventbus'
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
|
|
||||||
const call = ref({
|
|
||||||
id: '',
|
|
||||||
direction: '',
|
|
||||||
caller: '',
|
|
||||||
callee: '',
|
|
||||||
province: '',
|
|
||||||
city: '',
|
|
||||||
status: 'hanguped',
|
|
||||||
modtime: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
const qs = ref('')
|
|
||||||
|
|
||||||
const number = ref('')
|
|
||||||
|
|
||||||
const inWrapUp = ref(false);
|
|
||||||
|
|
||||||
const inCalling = computed(() => {
|
|
||||||
return call.value.status === 'answered'
|
|
||||||
})
|
|
||||||
|
|
||||||
const canHanguped = computed(() => {
|
|
||||||
return call.value.status !== 'hanguped'
|
|
||||||
})
|
|
||||||
|
|
||||||
const dialNumbers = computed(() => {
|
|
||||||
return ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'];
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 建立一个呼叫
|
|
||||||
* @param {String} caller
|
|
||||||
* @param {String} callee
|
|
||||||
* @param {String} direction
|
|
||||||
*/
|
|
||||||
const setupCall = (caller, callee, direction) => {
|
|
||||||
call.value.caller = caller;
|
|
||||||
call.value.callee = callee;
|
|
||||||
call.value.direction = direction;
|
|
||||||
call.value.modtime = (new Date()).getTime() / 1000;
|
|
||||||
call.value.status = 'setup';
|
|
||||||
if (!dialogVisible.value) {
|
|
||||||
dialogVisible.value = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新一个呼叫
|
|
||||||
* @param {String} status
|
|
||||||
*/
|
|
||||||
const updateCall = (status) => {
|
|
||||||
call.value.status = status;
|
|
||||||
call.value.modtime = (new Date()).getTime() / 1000;
|
|
||||||
if (status === 'hanguped') {
|
|
||||||
call.value.id = '';
|
|
||||||
if (dialogVisible.value) {
|
|
||||||
dialogVisible.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const updateCallStatus = (incall) => {
|
|
||||||
let appElement = document.getElementById('app');
|
|
||||||
if (appElement) {
|
|
||||||
if (incall) {
|
|
||||||
appElement.className = 'call-answering'
|
|
||||||
} else {
|
|
||||||
appElement.className = ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeypress = (e) => {
|
|
||||||
if (dialNumbers.value.indexOf(e.key) > -1) {
|
|
||||||
number.value += e.key
|
|
||||||
if (call.value.status === 'answered') {
|
|
||||||
Sip.sendDTMF(e.key).then(res => { }).catch(e => { })
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (e.keyCode === 8) {
|
|
||||||
if (number.value.length > 0) {
|
|
||||||
number.value = number.value.substring(0, number.value.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleInput = (e) => {
|
|
||||||
number.value += e
|
|
||||||
if (call.value.status === 'answered') {
|
|
||||||
Sip.sendDTMF(e).then(res => { }).catch(e => { })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClearInput = (e) => {
|
|
||||||
if (number.value.length > 0) {
|
|
||||||
number.value = number.value.substring(0, number.value.length - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleCallEvent = e => {
|
|
||||||
// 这里看后端推送的消息不做任何处理,全部变由sip信令进行触发
|
|
||||||
|
|
||||||
// let data = e.data;
|
|
||||||
// switch (e.event) {
|
|
||||||
// case 'CALL_CREATE':
|
|
||||||
// call.value.id = data.id;
|
|
||||||
// call.value.caller = data.caller;
|
|
||||||
// call.value.callee = data.callee;
|
|
||||||
// call.value.direction = data.direction;
|
|
||||||
// call.value.province = data.province;
|
|
||||||
// call.value.city = data.city;
|
|
||||||
// call.value.status = 'setup';
|
|
||||||
// call.value.modtime = (new Date()).getTime() / 1000;
|
|
||||||
// if (!data.forwarded) {
|
|
||||||
// if (!dialogVisible.value) {
|
|
||||||
// dialogVisible.value = true;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// case 'CALL_BRIDGE':
|
|
||||||
// updateCall('answered');
|
|
||||||
// updateCallStatus(true);
|
|
||||||
// break;
|
|
||||||
// case 'CALL_HANGUP':
|
|
||||||
// updateCall('hanguped');
|
|
||||||
// updateCallStatus(false);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleStateEvent = e => {
|
|
||||||
if (!e.data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e.data.state === 'wrapUp') {
|
|
||||||
inWrapUp.value = true;
|
|
||||||
} else {
|
|
||||||
inWrapUp.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const sipOffer = (callee) => {
|
|
||||||
const { uid } = useUserStore()
|
|
||||||
nextID().then(res => {
|
|
||||||
Sip.offer(callee, { 'sip_h_X-call_id': res.id }).then(res => {
|
|
||||||
setupCall(uid, callee, 'outbound');
|
|
||||||
ElMessage.success('发起成功')
|
|
||||||
number.value = '';
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message);
|
|
||||||
})
|
|
||||||
}).catch(e => {
|
|
||||||
Sip.offer(callee).then(res => {
|
|
||||||
setupCall(uid, callee, 'outbound');
|
|
||||||
ElMessage.success('发起成功')
|
|
||||||
number.value = '';
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
provide('sipOffer', sipOffer);
|
|
||||||
|
|
||||||
const handleOffer = e => {
|
|
||||||
const callee = number.value;
|
|
||||||
sipOffer(callee);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleEndWraup = e => {
|
|
||||||
const { uid } = useUserStore()
|
|
||||||
endWrapUp(uid).then(res => {
|
|
||||||
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleToggleHold = e => {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleHangup = e => {
|
|
||||||
const { uid } = useUserStore()
|
|
||||||
if (call.value.status === 'setup' && call.value.direction === 'inbound' && Sip.getIsRegistered()) {
|
|
||||||
Sip.decline().then(res => {
|
|
||||||
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (Sip.getIsRegistered()) {
|
|
||||||
Sip.hangup().then(res => {
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
hangup(uid).then(res => {
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
const { uid, getPassword, getAccessToken, setDeviceRegisterState, getAutoLoginDevice } = useUserStore()
|
|
||||||
let domain = 'test.cc.echo.me';
|
|
||||||
if (process.env.NODE_ENV === 'production') {
|
|
||||||
domain = window.location.hostname;
|
|
||||||
}
|
|
||||||
if (!getAutoLoginDevice()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
//如果登录设备才进行连接,否则的话不进行登录
|
|
||||||
Sip.connect(getBaseHost(), {
|
|
||||||
domain: domain,
|
|
||||||
urlPath: '/sip',
|
|
||||||
debug: false,
|
|
||||||
video: false,
|
|
||||||
qs: {
|
|
||||||
'access_token': getAccessToken(),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Sip.register(uid, getPassword()).then(res => {
|
|
||||||
|
|
||||||
}).catch(e => {
|
|
||||||
console.log(e);
|
|
||||||
})
|
|
||||||
|
|
||||||
Sip.on('EVENT_DEVICE_REGISTERED', (e) => {
|
|
||||||
setDeviceRegisterState(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
Sip.on('EVENT_DEVICE_UNREGISTERED', (e) => {
|
|
||||||
setDeviceRegisterState(false)
|
|
||||||
})
|
|
||||||
|
|
||||||
Sip.on('EVENT_CALL_RECEIVED', (e) => {
|
|
||||||
setupCall(e.caller, e.callee, e.direction);
|
|
||||||
})
|
|
||||||
|
|
||||||
Sip.on('EVENT_CALL_ANSWER', e => {
|
|
||||||
if (!call.value.id) {
|
|
||||||
number.value = '';
|
|
||||||
updateCall('answered');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Sip.on('EVENT_CALL_HANGUP', e => {
|
|
||||||
if (!call.value.id) {
|
|
||||||
number.value = '';
|
|
||||||
updateCall('hanguped');
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
bus.on("sip_offer", e => {
|
|
||||||
if (typeof (e) === 'object') {
|
|
||||||
if (e.callee && typeof e.callee === 'string') {
|
|
||||||
sipOffer(e.callee);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
Connection.on('call_event', handleCallEvent)
|
|
||||||
Connection.on('user_state', handleStateEvent)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
Connection.off('call_event', handleCallEvent)
|
|
||||||
Connection.on('user_state', handleStateEvent)
|
|
||||||
})
|
|
||||||
|
|
||||||
</script>
|
|
|
@ -1,30 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="notice-container">
|
|
||||||
<div class="notice-body">
|
|
||||||
{{ data.content }}
|
|
||||||
</div>
|
|
||||||
<div class="notice-footer">
|
|
||||||
<el-button type="primary" @click="handleReadMsg">收到</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
data: {
|
|
||||||
type: Object,
|
|
||||||
default: () => {
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['done'])
|
|
||||||
|
|
||||||
const handleReadMsg = (e) => {
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -1,229 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="popup-dialog" :style="{ left: position.left + 'px', top: position.top + 'px' }" v-if="modelValue"
|
|
||||||
:class="needAnswer ? 'noanswer' : ''">
|
|
||||||
<div class="popup-header" @mousedown="handleDragStart">
|
|
||||||
<span class="text-muted cursor-pointer" @click="handleClose">×</span>
|
|
||||||
</div>
|
|
||||||
<div class="popup-container">
|
|
||||||
<div class="popup-avatar">
|
|
||||||
<el-avatar :size="64" :src="avatar">
|
|
||||||
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHgAAAB4CAYAAAA5ZDbSAAAAAXNSR0IArs4c6QAACCJJREFUeAHtnUlvFEcUx1/P5rHx7vEy3o3B2EzCkoUTpxws8T245MoHyS3iwveI5APKgRMKEkSxkJAiEtZgDIF4ADMwTOrf0NHMZLaa7qrq17wnjQb3dNd79f911avqrm48arJr12+WqpXqRbV5u0a0TFQbbtpF/oyVAl7ZI7qnQtpJ59JXzp87s1sfnvrto+3u7ub2/z78gTz6vlarpYLt8s1HAc/zPlCNLhcm8pdKpVIFkfuAfbgvDn9SYL/jUx2JtJ0CCvTVwnj+AiD7LRUtV+C2k4vfdrD0e2MVuufn3HfVX6Vb5geyU8TortPZ9KkMBlQ1kpzbSSyOv6HBgi266G2OFZCYe1JgO/VxKtTTzrITMwXAVrVgmecy46YRbm3YH0VrHCG7MlNAADMDphuuANZVjNn+ApgZMN1wBbCuYsz2F8DMgOmGK4B1FWO2vwBmBkw3XAGsqxiz/QUwM2C64QpgXcWY7S+AmQHTDVcA6yrGbH8BzAyYbrgCWFcxZvtnmMWrHe7oyDAVpsZpZHiIcrksDagP7G3lHVXU56D8mvafvaB/DsraZXM4IJGA1YIzWpyfUZ9ZH2orEEODaRoazNP42AgtLcz6sB88ekIPHu2RWs/U6hCW2xIHGK11fW2J8gM5LSBo3UdXF2m+OEO/373vt2qtAmK6c6Jy8OryPJU217Xh1rPBiYEyUFYSLDGAtzbWaGWpGBkTlIUyuVsiAKO1zUxPRs4CZXJvyewBI+dG2XKbzxKUDR9cjTVgjJYxoDJt8AFfHI01YEyFdEfL/UCCD/jiaMwBz1rTHHNqjsYWMK5QYe5qy+ALPrkZW8AuBj4ufIY9odgCxrVl2+bCZ9g6sgVss3sORHbhM/Dd7zdbwMFdoX4r3s9xLnz2E2f9MWwB11fC1r853mRiCxj3c21b5Z19n2HryBYwbtbbNhc+w9aRLWCsxLBtB+VXtl2G9scWMJbZ2Lb9Zy9tuwztjy1grKGy2WXCF8d1W2wB49TGGipbZtNXlHViDniPDt/679yMUpP/lQUfWIzH0VgDxupHLJAzbfDBdaUla8AAi8HWn/cfG2OMsl0M6KKqEHvAEOKPe49o7+nzqDT5rxyUibI5WyIAA8DtO3cjbclouSiTuyVq4TtaW/nV674WvgcgMaBK0sL3RAEGJOTLZ89fdn10JQAafGOeK4+uBGrE/Bsj3vsPn/gfefgs5rDChoerTxyvQIWtd3B8YgZZQYXku1EBAdyoR+L+EsCJQ9pYIbaAUymP5mYL9PWZLZoYH22sVYR/DQ3lKZ1OR1ii3aLYTZOwsnGhOE3F2WnKZj+Gv7mxSjdu3o789iF8nf5ig1Jeih4+3vOnUe/fV+0SCunN+/naLyzeV5BKpfynCPG6hVYPgr14eUC3frsTUo7GwwEXr3gIrFqtKtBP1fTrL+ICmkUXjS74m7MnaXlxriVcAACIKJ/lRVn1cOEDXTVi+PZsiSYnxrAp9hZrwJlMhjaPr9Kp0nEazA90FRPiR5GPUQbKamfour88eYw21pcJPUucLbbRDao34Hx1epNmZ6Z61g9dN/JxmCcQcCzKaJUGmgMpzk37PUucH2mJJeCx0WE6e+pET622WfRcNhvq3Rp4LwfK6NXQszTn6l6PtbFf7ABPFyb8Ljmruud+rd983Crv9hIDcjO67MJk/F71ECvAmNeiBUWR13Tzcbe82w00Yj65eVQrpXQrM4rfYwMYrQ6Dll5yXy8V18nHOnm3k2/4PHFshaZi1JJjARgDqpI6+6OCG0DoNR/r5t2g/Fbf/omlRv54TWIczDlgTIWQv/Btwrrl437zbqdYM5k0lbbWY3GJ0zngLTUl6WWO20nQbr+1y8dh824nv2jBcXhTnlPAGDHbuCLUKh9HlXc7QZ6aHKM5jXl8p7L6/c0Z4HQ6Revq7a62rDkfR5l3O9VhbWVBddXOZCZnnleXF2hA85W/nYTs5bcgH5vIu+38o6dYWXL35lozI5t2tf20/cjQoH/Lr8tuRn7udI3ZiENV6IJ6B/XjJ/v05s2hKRdty3XSghfb3PJrG2WEPyAfRz0d6xYeFiesqbtTLsw6YEwhZtTg6nMzXPwIFijYrLt1wEV1OTKKS5E2RYrCl7/EaKYQRVFaZVgHPK9usX2uVpxLOGBcWMj3cOM+qScALuhgJG/TrLbgiXG7lbMpZK++bN+IsAp4bFQAYzGDTbMGGAOrOC9tsSX68JFBq4NMa4Bx5tqef9qCpuMHGoyNHNE5JNS+VgGHijRBB49a7KatAbZ93TnO54Pp26P1dbcGOMwiuvqAk/Bvm1e07AH+9BxREgCFrYMADqtgzI+32ZvZa8GG1lzFnGXL8Gy2YGv3g2/cut2ysrLRrALWAB8evjVbEym9pQLWuuiW3mWjcQUEsHGJ3ToQwG71N+5dABuX2K0DAexWf+PeBbBxid06EMBu9TfuXQAbl9itAwHsVn/j3gWwcYndOhDAbvU37l0AG5fYrQMB7FZ/494FsHGJ3ToQwG71N+5dABuX2K0DAexWf+PeBbBxid06EMBu9TfuXQAbl9itAwXYK7sNQbybU8Arq/e/0D1zDqRklwqALbroHZdBiG+jCuyk0rn0FfXM6gejbqRw6wqAKdimzp87s0s1umw9AnFoVgHFFGz9UXRhIn9JEb9q1qOUbksBsART+PMBl0qlSmE8f0G95O9H6a5tYYjeD9iBIViCKTyogVajXbt+s1StVC+qrdvq/7xbJqrZfS1MYzjyV1cFvPKnmdAOcq6fcuuO+RcbTpTXEDYkmgAAAABJRU5ErkJggg==">
|
|
||||||
</el-avatar>
|
|
||||||
<h3>{{ displayNumber }}</h3>
|
|
||||||
</div>
|
|
||||||
<div class="popup-content">
|
|
||||||
<div class="popup-info">
|
|
||||||
<span>{{ model.province }}</span> <span>{{ model.city }}</span>
|
|
||||||
</div>
|
|
||||||
<time class="popup-timer text-muted"> {{ duration }}</time>
|
|
||||||
</div>
|
|
||||||
<div class="popup-actions">
|
|
||||||
<el-button type="success" :disable="answering" v-if="needAnswer" size="large" circle title="接听">
|
|
||||||
<icon name="phone" @click="handleAnswer"></icon>
|
|
||||||
</el-button>
|
|
||||||
<el-button type="warning" :disable="holding" v-if="inCalling" size="large" circle :title="holdLabel">
|
|
||||||
<icon :name="isHold ? 'unmuted' : 'muted'" @click="handleToggleHold"></icon>
|
|
||||||
</el-button>
|
|
||||||
<el-button type="danger" :disable="hanguping" size="large" circle title="挂断">
|
|
||||||
<icon name="phone" @click="handleHangup"></icon>
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
|
||||||
import { hold, unhold, hangup } from '@/apis/control'
|
|
||||||
import useUserStore from '@/stores/user'
|
|
||||||
import { durationFormat } from '@/assets/js/formatter'
|
|
||||||
import Icon from '@/components/widgets/Icon.vue';
|
|
||||||
import Sip from '@/assets/js/sip'
|
|
||||||
import { ElMessage } from 'element-plus';
|
|
||||||
import { getUserAvatar, getUserInfo } from '@/apis/organize';
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
model: {
|
|
||||||
type: Object,
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const isHold = ref(false);
|
|
||||||
|
|
||||||
const duration = ref('');
|
|
||||||
|
|
||||||
const answering = ref(false);
|
|
||||||
|
|
||||||
const holding = ref(false);
|
|
||||||
|
|
||||||
const hanguping = ref(false);
|
|
||||||
|
|
||||||
const dragging = ref(false);
|
|
||||||
|
|
||||||
const avatar = ref('');
|
|
||||||
|
|
||||||
const position = ref({ left: 0, top: 0 });
|
|
||||||
|
|
||||||
const prevDragPosition = ref({ x: 0, y: 0 });
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue']);
|
|
||||||
|
|
||||||
const needAnswer = computed(() => {
|
|
||||||
if (!Sip.getIsRegistered()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (props.model.status !== 'setup') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (props.model.direction === 'inbound') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const { uid } = useUserStore();
|
|
||||||
if (props.model.direction === 'inner' && props.model.callee === uid) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
})
|
|
||||||
|
|
||||||
const inCalling = computed(() => {
|
|
||||||
return props.model.status === 'answered';
|
|
||||||
})
|
|
||||||
|
|
||||||
const holdLabel = computed(() => {
|
|
||||||
return isHold.value ? '接回' : '保持';
|
|
||||||
})
|
|
||||||
|
|
||||||
const displayNumber = ref('');
|
|
||||||
|
|
||||||
const timerTicket = () => {
|
|
||||||
if (props.model.modtime > 0) {
|
|
||||||
duration.value = durationFormat(((new Date()).getTime() / 1000) - props.model.modtime, ':');
|
|
||||||
}
|
|
||||||
setTimeout(timerTicket, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = (e) => {
|
|
||||||
avatar.value = '';
|
|
||||||
emit('update:modelValue', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const handleAnswer = (e) => {
|
|
||||||
answering.value = true;
|
|
||||||
Sip.answer().then(res => {
|
|
||||||
answering.value = false;
|
|
||||||
}).catch(e => {
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
answering.value = false;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleToggleHold = (e) => {
|
|
||||||
const { uid } = useUserStore();
|
|
||||||
holding.value = true
|
|
||||||
if (isHold.value) {
|
|
||||||
unhold(uid).then(res => {
|
|
||||||
holding.value = false
|
|
||||||
isHold.value = false
|
|
||||||
}).catch(e => {
|
|
||||||
holding.value
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
hold(uid).then(res => {
|
|
||||||
holding.value
|
|
||||||
isHold.value = true
|
|
||||||
}).catch(e => {
|
|
||||||
holding.value
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleHangup = (e) => {
|
|
||||||
const { uid } = useUserStore()
|
|
||||||
hanguping.value = true;
|
|
||||||
if (props.model.status === 'setup' && props.model.direction === 'inbound' && Sip.getIsRegistered()) {
|
|
||||||
Sip.decline().then(res => {
|
|
||||||
hanguping.value = false
|
|
||||||
}).catch(e => {
|
|
||||||
hanguping.value = false
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (Sip.getIsRegistered()) {
|
|
||||||
Sip.hangup().then(res => {
|
|
||||||
hanguping.value = false
|
|
||||||
}).catch(e => {
|
|
||||||
hanguping.value = false
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
hangup(uid).then(res => {
|
|
||||||
hanguping.value = false
|
|
||||||
}).catch(e => {
|
|
||||||
hanguping.value = false
|
|
||||||
ElMessage.error(e.message)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDragStart = (e) => {
|
|
||||||
dragging.value = true
|
|
||||||
prevDragPosition.value.x = e.clientX
|
|
||||||
prevDragPosition.value.y = e.clientY
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDragging = (e) => {
|
|
||||||
if (!dragging.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let x = e.clientX - prevDragPosition.value.x;
|
|
||||||
let y = e.clientY - prevDragPosition.value.y;
|
|
||||||
prevDragPosition.value.x = e.clientX;
|
|
||||||
prevDragPosition.value.y = e.clientY;
|
|
||||||
position.value.left += x;
|
|
||||||
position.value.top += y;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDragged = (e) => {
|
|
||||||
dragging.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
position.value.left = (window.innerWidth - 280) / 2
|
|
||||||
position.value.top = window.innerHeight / 10
|
|
||||||
document.addEventListener('mousemove', handleDragging)
|
|
||||||
document.addEventListener('mouseup', handleDragged)
|
|
||||||
const { uid } = useUserStore()
|
|
||||||
|
|
||||||
watch(() => { return props.model }, (val) => {
|
|
||||||
if (val.direction === 'inbound') {
|
|
||||||
displayNumber.value = val.caller;
|
|
||||||
} else if (val.direction === 'outbound') {
|
|
||||||
displayNumber.value = val.callee;
|
|
||||||
} else if (val.direction === 'inner') {
|
|
||||||
if (val.caller === uid) {
|
|
||||||
displayNumber.value = val.callee;
|
|
||||||
avatar.value = getUserAvatar(val.callee);
|
|
||||||
} else {
|
|
||||||
displayNumber.value = val.caller;
|
|
||||||
avatar.value = getUserAvatar(val.caller);
|
|
||||||
}
|
|
||||||
getUserInfo(displayNumber.value).then(res => {
|
|
||||||
displayNumber.value = `${res.username}(${res.id})`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, { deep: true })
|
|
||||||
timerTicket();
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
document.removeEventListener('mousemove', handleDragging)
|
|
||||||
document.removeEventListener('mouseup', handleDragged)
|
|
||||||
})
|
|
||||||
</script>
|
|
|
@ -10,7 +10,7 @@ const useSystemStore = defineStore('system', {
|
||||||
lang: 'zh-CN',
|
lang: 'zh-CN',
|
||||||
logoUrl: '//s3.tebi.io/tenos/images/logo/jc.png',
|
logoUrl: '//s3.tebi.io/tenos/images/logo/jc.png',
|
||||||
copyright: '2005-2023 JUSTCALL 版权 © 2023 集时股份呼叫中心开发团队',
|
copyright: '2005-2023 JUSTCALL 版权 © 2023 集时股份呼叫中心开发团队',
|
||||||
productName: '呼叫中心平台',
|
productName: '在线系统',
|
||||||
variables: {},
|
variables: {},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@ const useUserStore = defineStore('user', {
|
||||||
pwdhash: '',
|
pwdhash: '',
|
||||||
accessToken: '*',
|
accessToken: '*',
|
||||||
tokenExpiredAt: 0,
|
tokenExpiredAt: 0,
|
||||||
|
isAdmin: false,
|
||||||
permissions: [],
|
permissions: [],
|
||||||
description: '',
|
description: '',
|
||||||
loginDevice: false,
|
loginDevice: false,
|
||||||
|
@ -56,6 +57,9 @@ const useUserStore = defineStore('user', {
|
||||||
setDeviceRegisterState(ok) {
|
setDeviceRegisterState(ok) {
|
||||||
this.deviceRegistered = ok
|
this.deviceRegistered = ok
|
||||||
},
|
},
|
||||||
|
setIsAdmin(b) {
|
||||||
|
this.isAdmin = b;
|
||||||
|
},
|
||||||
setPermissions(s) {
|
setPermissions(s) {
|
||||||
if (typeof s === 'string') {
|
if (typeof s === 'string') {
|
||||||
try {
|
try {
|
||||||
|
@ -84,7 +88,7 @@ const useUserStore = defineStore('user', {
|
||||||
getAccessToken() {
|
getAccessToken() {
|
||||||
return this.accessToken;
|
return this.accessToken;
|
||||||
},
|
},
|
||||||
getAutoLoginDevice() {
|
getAutoLoginDevice() {
|
||||||
return this.loginDevice
|
return this.loginDevice
|
||||||
},
|
},
|
||||||
getPassword() {
|
getPassword() {
|
||||||
|
@ -98,6 +102,9 @@ const useUserStore = defineStore('user', {
|
||||||
},
|
},
|
||||||
hasPermission(permission) {
|
hasPermission(permission) {
|
||||||
let ret = false
|
let ret = false
|
||||||
|
if (this.isAdmin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (typeof permission === 'boolean') {
|
if (typeof permission === 'boolean') {
|
||||||
ret = permission
|
ret = permission
|
||||||
} else if (Array.isArray(permission)) {
|
} else if (Array.isArray(permission)) {
|
||||||
|
|
|
@ -64,8 +64,9 @@ const submit = () => {
|
||||||
}
|
}
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
userLogin(model.value.username, model.value.password).then(res => {
|
userLogin(model.value.username, model.value.password).then(res => {
|
||||||
const { setUserID, setAccessToken, setPassword, setUsername, setPermissions, setAvatar, setAutoLoginDevice } = useUserStore()
|
const { setUserID, setIsAdmin, setAccessToken, setPassword, setUsername, setPermissions, setAvatar, setAutoLoginDevice } = useUserStore()
|
||||||
setUserID(res.uid);
|
setUserID(res.uid);
|
||||||
|
setIsAdmin(res.admin);
|
||||||
setAccessToken(res.token, res.expire_in);
|
setAccessToken(res.token, res.expire_in);
|
||||||
setPassword(model.value.password);
|
setPassword(model.value.password);
|
||||||
setAutoLoginDevice(model.value.remember);
|
setAutoLoginDevice(model.value.remember);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<viewer :title="title" :module-name="moduleName" :table-name="tableName" :disable-toolbar="false"
|
<viewer :title="title" :module-name="moduleName" :table-name="tableName" :disable-toolbar="false"
|
||||||
defaultSortable="id" formMode="drawer" :disablePermission="true">
|
defaultSortable="id" formMode="drawer">
|
||||||
<template #crudform="{ schema, model }">
|
<template #crudform="{ schema, model }">
|
||||||
<el-tree ref="treeElement" :data="permissions" v-if="schema.column == 'permissions'" node-key="id"
|
<el-tree ref="treeElement" :data="permissions" v-if="schema.column == 'permissions'" node-key="id"
|
||||||
v-model="model.permissions" :props="treeOptions" @check-change="handleTreeChange(model)"
|
v-model="model.permissions" :props="treeOptions" @check-change="handleTreeChange(model)"
|
||||||
|
|
|
@ -21,46 +21,47 @@
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="18">
|
<el-col :span="18">
|
||||||
<div class="profile-tabs">
|
<div class="profile-tabs">
|
||||||
<el-tabs v-model="activeTab">
|
<el-tabs v-model="activeTab">
|
||||||
<el-tab-pane label="基本资料" name="basic">
|
<el-tab-pane label="基本资料" name="basic">
|
||||||
<el-form ref="basicForm" :model="basicModel" status-icon label-width="120px" class="segment-form">
|
<el-form ref="basicForm" :model="basicModel" status-icon label-width="120px"
|
||||||
<el-form-item label="用户名" prop="oldPassword">
|
class="segment-form">
|
||||||
<el-input v-model="basicModel.username" autocomplete="off" placeholder="请输入用户名" />
|
<el-form-item label="用户名" prop="oldPassword">
|
||||||
</el-form-item>
|
<el-input v-model="basicModel.username" autocomplete="off" placeholder="请输入用户名" />
|
||||||
<el-form-item label="邮箱" prop="newPassword">
|
</el-form-item>
|
||||||
<el-input v-model="basicModel.email" autocomplete="off" placeholder="请输入邮箱" />
|
<el-form-item label="邮箱" prop="newPassword">
|
||||||
</el-form-item>
|
<el-input v-model="basicModel.email" autocomplete="off" placeholder="请输入邮箱" />
|
||||||
<el-form-item label="个人简介" prop="confirmPassword">
|
</el-form-item>
|
||||||
<el-input v-model="basicModel.description" type="textarea" autocomplete="off" rows="10"
|
<el-form-item label="个人简介" prop="confirmPassword">
|
||||||
placeholder="请介绍下自己" />
|
<el-input v-model="basicModel.description" type="textarea" autocomplete="off"
|
||||||
</el-form-item>
|
rows="10" placeholder="请介绍下自己" />
|
||||||
<el-form-item>
|
</el-form-item>
|
||||||
<el-button type="primary" @click="handleUpdateProfile">修改</el-button>
|
<el-form-item>
|
||||||
</el-form-item>
|
<el-button type="primary" @click="handleUpdateProfile">修改</el-button>
|
||||||
</el-form>
|
</el-form-item>
|
||||||
</el-tab-pane>
|
</el-form>
|
||||||
<el-tab-pane label="密码修改" name="password">
|
</el-tab-pane>
|
||||||
<el-form ref="passwordForm" :model="passwordModel" :rules="passwordRules" status-icon
|
<el-tab-pane label="密码修改" name="password">
|
||||||
label-width="120px" class="segment-form">
|
<el-form ref="passwordForm" :model="passwordModel" :rules="passwordRules" status-icon
|
||||||
<el-form-item label="旧密码" prop="oldPassword">
|
label-width="120px" class="segment-form">
|
||||||
<el-input v-model="passwordModel.oldPassword" type="password" autocomplete="off"
|
<el-form-item label="旧密码" prop="oldPassword">
|
||||||
placeholder="请输入旧密码" />
|
<el-input v-model="passwordModel.oldPassword" type="password" autocomplete="off"
|
||||||
</el-form-item>
|
placeholder="请输入旧密码" />
|
||||||
<el-form-item label="新密码" prop="newPassword">
|
</el-form-item>
|
||||||
<el-input v-model="passwordModel.newPassword" type="password" autocomplete="off"
|
<el-form-item label="新密码" prop="newPassword">
|
||||||
placeholder="请输入新密码" />
|
<el-input v-model="passwordModel.newPassword" type="password" autocomplete="off"
|
||||||
</el-form-item>
|
placeholder="请输入新密码" />
|
||||||
<el-form-item label="确认密码" prop="confirmPassword">
|
</el-form-item>
|
||||||
<el-input v-model="passwordModel.confirmPassword" type="password" autocomplete="off"
|
<el-form-item label="确认密码" prop="confirmPassword">
|
||||||
placeholder="请再次输入新密码" />
|
<el-input v-model="passwordModel.confirmPassword" type="password" autocomplete="off"
|
||||||
</el-form-item>
|
placeholder="请再次输入新密码" />
|
||||||
<el-form-item>
|
</el-form-item>
|
||||||
<el-button type="primary" @click="handleResetPassword">修改</el-button>
|
<el-form-item>
|
||||||
</el-form-item>
|
<el-button type="primary" @click="handleResetPassword">修改</el-button>
|
||||||
</el-form>
|
</el-form-item>
|
||||||
</el-tab-pane>
|
</el-form>
|
||||||
</el-tabs>
|
</el-tab-pane>
|
||||||
</div>
|
</el-tabs>
|
||||||
|
</div>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</div>
|
</div>
|
||||||
|
@ -75,7 +76,7 @@
|
||||||
box-shadow: var(--el-box-shadow-light);
|
box-shadow: var(--el-box-shadow-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-tabs{
|
.profile-tabs {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: var(--el-box-shadow-light);
|
box-shadow: var(--el-box-shadow-light);
|
||||||
|
|
|
@ -15,4 +15,15 @@ export default defineConfig({
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'release',
|
||||||
|
assetsDir: 'static',
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks(id) {
|
||||||
|
return 'notebook'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue