add admin users
This commit is contained in:
parent
b49c431a8e
commit
46ee4ddd6f
92
api.go
92
api.go
|
@ -1,8 +1,10 @@
|
|||
package moto
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"git.nobla.cn/golang/kos"
|
||||
"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/internal/organize"
|
||||
"git.nobla.cn/golang/moto/internal/organize/passport"
|
||||
|
@ -11,9 +13,20 @@ import (
|
|||
"git.nobla.cn/golang/rest"
|
||||
restTypes "git.nobla.cn/golang/rest/types"
|
||||
"gorm.io/gorm"
|
||||
httpkg "net/http"
|
||||
"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) {
|
||||
var (
|
||||
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 {
|
||||
return ctx.Error(http.ErrPermissionDenied, err.Error())
|
||||
}
|
||||
ctx.SetCookie(&httpkg.Cookie{Name: organize.CookieName, Value: tk.Token, Path: "/"})
|
||||
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) {
|
||||
passport.Logout(ctx.Context(), ctx.User().Get("token"))
|
||||
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 {
|
||||
return ctx.Error(http.ErrTemporaryUnavailable, err.Error())
|
||||
}
|
||||
if arrays.Exists(ctx.User().ID, svr.cfg.AdminUsers) {
|
||||
profile.Admin = true
|
||||
}
|
||||
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) {
|
||||
return ctx.Success(map[string]string{})
|
||||
}
|
||||
|
@ -57,16 +106,16 @@ func (svr *Server) handleListSchema(ctx *http.Context) (err error) {
|
|||
schemas, err = rest.GetSchemas(
|
||||
ctx.Request().Context(),
|
||||
db.WithContext(ctx.Request().Context()),
|
||||
ctx.User().Get("domain"),
|
||||
version.ProductName,
|
||||
"",
|
||||
version.ModuleName,
|
||||
ctx.Param("table"),
|
||||
)
|
||||
} else {
|
||||
schemas, err = rest.VisibleSchemas(
|
||||
ctx.Request().Context(),
|
||||
db.WithContext(ctx.Request().Context()),
|
||||
ctx.User().Get("domain"),
|
||||
version.ProductName,
|
||||
"",
|
||||
version.ModuleName,
|
||||
ctx.Param("table"),
|
||||
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() {
|
||||
|
||||
kos.Http().Use(organize.AuthMiddleware)
|
||||
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.MethodPut, "/passport/reset-password", svr.handleResetPassword)
|
||||
kos.Http().Handle(http.MethodDelete, "/passport/logout", svr.handleLogout)
|
||||
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, "/rest/schema/:module/:table", svr.handleListSchema)
|
||||
kos.Http().Handle(http.MethodPut, "/rest/schema/:module/:table", svr.handleSaveSchema)
|
||||
kos.Http().Handle(http.MethodGet, "/rest/schema/:table", svr.handleListSchema)
|
||||
kos.Http().Handle(http.MethodPut, "/rest/schema/:table", svr.handleSaveSchema)
|
||||
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 (
|
||||
"context"
|
||||
"git.nobla.cn/golang/kos"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -31,7 +31,7 @@ func TryCache(ctx context.Context, key string, f CachingFunc, cbs ...CacheOption
|
|||
var (
|
||||
ok bool
|
||||
hasDependValue bool
|
||||
dependValue any
|
||||
dependValue string
|
||||
)
|
||||
opts := &CacheOptions{}
|
||||
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 {
|
||||
hasDependValue = true
|
||||
if reflect.DeepEqual(entry.compareValue, dependValue) {
|
||||
if entry.compareValue == dependValue {
|
||||
entry.lastChecked = time.Now()
|
||||
return entry.storeValue, nil
|
||||
} else {
|
||||
|
@ -104,6 +104,9 @@ func Init(ctx context.Context, cfg config.Database, plugins ...gorm.Plugin) (err
|
|||
return
|
||||
}
|
||||
db = db.WithContext(ctx)
|
||||
if kos.Debug() {
|
||||
db = db.Debug()
|
||||
}
|
||||
for _, plugin := range plugins {
|
||||
if err = db.Use(plugin); err != nil {
|
||||
return
|
||||
|
|
|
@ -17,7 +17,7 @@ type (
|
|||
|
||||
cacheEntry struct {
|
||||
storeValue any
|
||||
compareValue any
|
||||
compareValue string
|
||||
createdAt time.Time
|
||||
lastChecked time.Time
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package config
|
||||
|
||||
import "os"
|
||||
|
||||
type Database struct {
|
||||
Address string `json:"address" yaml:"address"`
|
||||
Username string `json:"username" yaml:"username"`
|
||||
|
@ -7,11 +9,18 @@ type Database struct {
|
|||
Database string `json:"database" yaml:"database"`
|
||||
}
|
||||
|
||||
type Avatar struct {
|
||||
Dirname string `json:"dirname" yaml:"dirname"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Database Database `json:"database" yaml:"database"`
|
||||
Avatar Avatar `json:"avatar" yaml:"avatar"`
|
||||
AdminUsers []string `json:"admin_users" yaml:"adminUsers"`
|
||||
}
|
||||
|
||||
func New() *Config {
|
||||
cfg := &Config{}
|
||||
cfg.Avatar.Dirname = os.TempDir()
|
||||
return cfg
|
||||
}
|
||||
|
|
|
@ -3,3 +3,6 @@ database:
|
|||
username: "root"
|
||||
password: "root"
|
||||
database: "test2"
|
||||
|
||||
adminUsers:
|
||||
- "1000"
|
7
go.mod
7
go.mod
|
@ -4,7 +4,8 @@ go 1.22.9
|
|||
|
||||
require (
|
||||
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/google/uuid v1.6.0
|
||||
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/universal-translator v0.18.1 // 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/now v1.1.5 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
|
@ -30,7 +32,8 @@ require (
|
|||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.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/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=
|
||||
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.2 h1:b5hmGuVb1zXFQ8O2AYCi8628JGftgH2TL5BLftCN1OU=
|
||||
git.nobla.cn/golang/rest v0.0.2/go.mod h1:tGDOul2GGJtxk6fAeu+YLpMt/Up/TsBonTkygymN/wE=
|
||||
git.nobla.cn/golang/rest v0.0.4 h1:i8hD/z5UAgoKjRA0ED83yK0q0IuYJ+xiiUZ1nGdn8PY=
|
||||
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/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/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/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
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.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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=
|
||||
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/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/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=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
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 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
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
|
||||
}
|
||||
|
||||
func DepartmentUserNested(ctx context.Context, domainName string) []*types.NestedValue {
|
||||
func DepartmentUserNested(ctx context.Context) []*types.NestedValue {
|
||||
var (
|
||||
err error
|
||||
value any
|
||||
|
@ -55,21 +55,21 @@ func DepartmentUserNested(ctx context.Context, domainName string) []*types.Neste
|
|||
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)
|
||||
err = tx.Where("domain=?", domainName).Order("created_at ASC").Find(&departments).Error
|
||||
err = tx.Order("created_at ASC").Find(&departments).Error
|
||||
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)
|
||||
} else {
|
||||
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)
|
||||
err = tx.Where("domain=?", domainName).Order("uid ASC").Find(&users).Error
|
||||
err = tx.Order("uid ASC").Find(&users).Error
|
||||
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)
|
||||
} else {
|
||||
return nil
|
||||
|
@ -96,15 +96,15 @@ func DepartmentUserNested(ctx context.Context, domainName string) []*types.Neste
|
|||
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) {
|
||||
func DepartmentTypes(ctx context.Context) []*types.TypeValue {
|
||||
result, err := db.TryCache(ctx, fmt.Sprintf("department:types"), func(tx *gorm.DB) (any, error) {
|
||||
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
|
||||
} else {
|
||||
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 {
|
||||
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"
|
||||
"git.nobla.cn/golang/moto/common/db"
|
||||
"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) {
|
||||
|
@ -15,3 +16,25 @@ func Profile(ctx context.Context, uid string) (profile *types.Profile, err error
|
|||
First(profile).Error
|
||||
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"
|
||||
)
|
||||
|
||||
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))
|
||||
func RoleTypes(ctx context.Context) []*types.TypeValue {
|
||||
result, err := db.TryCache(ctx, fmt.Sprintf("role:types"), func(tx *gorm.DB) (any, error) {
|
||||
return rest.ModelTypes(ctx, tx, &models.Role{}, "", "name", "id"), nil
|
||||
}, db.WithDepend("SELECT max(`updated_at`) FROM `roles`"))
|
||||
if err == nil {
|
||||
return result.([]*types.TypeValue)
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ type (
|
|||
Role string `json:"role"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar"`
|
||||
Admin bool `json:"admin"`
|
||||
Description string `json:"description"`
|
||||
Permissions string `json:"permissions"`
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
"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) {
|
||||
func UserTypes(ctx context.Context) []*types.TypeValue {
|
||||
result, err := db.TryCache(ctx, fmt.Sprintf("user:types"), func(tx *gorm.DB) (any, error) {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -24,9 +24,34 @@ func UserTypes(ctx context.Context, domainName string) []*types.TypeValue {
|
|||
})
|
||||
}
|
||||
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 {
|
||||
return result.([]*types.TypeValue)
|
||||
}
|
||||
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/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"
|
||||
|
@ -35,10 +36,13 @@ func (svr *Server) prepare() (err error) {
|
|||
if err = db.DB().AutoMigrate(item); err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
if err = migrate(svr.ctx); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -3,4 +3,5 @@ package version
|
|||
var (
|
||||
ProductName = "moto"
|
||||
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) {
|
||||
return request.put('/organize/profile', data)
|
||||
return request.put('/user/profile', data)
|
||||
}
|
||||
|
||||
export function resetPassword(oldPwd, newPwd) {
|
||||
return request.put('/organize/password', {
|
||||
return request.put('/passport/reset-password', {
|
||||
old_password: oldPwd,
|
||||
new_password: newPwd
|
||||
})
|
||||
|
@ -53,22 +53,22 @@ export function getDepartmentUsers(id) {
|
|||
}
|
||||
|
||||
export function getUsers(id) {
|
||||
return request.get(`/organize/user-list`)
|
||||
return request.get(`/organize/user-list`);
|
||||
}
|
||||
|
||||
export function getClientHello() {
|
||||
return request.post(`/organize/client-hello`)
|
||||
return request.post(`/organize/client-hello`);
|
||||
}
|
||||
|
||||
export function getUserAvatar(uid) {
|
||||
return `${getBaseUrl()}/organize/avatar/${uid}`
|
||||
return `${getBaseUrl()}/organize/avatar/${uid}`;
|
||||
}
|
||||
|
||||
export function setUserAttr(name, value) {
|
||||
let data = '';
|
||||
if (typeof value !== 'string') {
|
||||
try {
|
||||
data = JSON.stringify(value)
|
||||
data = JSON.stringify(value);
|
||||
} catch (e) {
|
||||
data = value
|
||||
}
|
||||
|
|
|
@ -25,54 +25,6 @@ let statusMap = [
|
|||
"value": "holiday",
|
||||
"color": "#b3261e",
|
||||
"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 {
|
||||
width: var(--form-control-width);
|
||||
|
||||
|
@ -520,13 +524,14 @@ a {
|
|||
.avatar-list {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.el-avatar{
|
||||
.el-avatar {
|
||||
margin-right: .25rem;
|
||||
margin-bottom: .25rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.avatar-footer{
|
||||
|
||||
.avatar-footer {
|
||||
text-align: right;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
|
|
@ -182,6 +182,7 @@ class CRUD {
|
|||
resolve(this, this.schemas)
|
||||
return
|
||||
}
|
||||
if (this.modelName != "") {
|
||||
httpclicent.get(`/rest/schema/${this.modelName}/${this.tableName}`).then(res => {
|
||||
this.schemas = res;
|
||||
this.__prepare()
|
||||
|
@ -189,6 +190,15 @@ class CRUD {
|
|||
}).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",
|
||||
permissions: [
|
||||
{
|
||||
|
|
|
@ -12,11 +12,6 @@
|
|||
<li>
|
||||
<icon name="fullscreen" @click="handleFullscreen"></icon>
|
||||
</li>
|
||||
<li>
|
||||
<el-badge :value="unreadMsgCount" :hidden="unreadMsgCount <= 0">
|
||||
<icon name="message" @click="handleListNotices"></icon>
|
||||
</el-badge>
|
||||
</li>
|
||||
<li style="display: none;">
|
||||
<el-dropdown @command="handleChangeLang">
|
||||
<icon name="network"></icon>
|
||||
|
@ -79,17 +74,17 @@ import Icon from '@/components/widgets/Icon.vue';
|
|||
import { useRouter } from 'vue-router'
|
||||
import { getUserStatus, getStatusText, getStatusTextColor } from '@/assets/js/status'
|
||||
import { ElNotification } from 'element-plus';
|
||||
import Notice from './parts/Notice.vue';
|
||||
|
||||
|
||||
const systemStore = useSystemStore();
|
||||
const themeStore = useThemeStore();
|
||||
const userStore = useUserStore();
|
||||
const userState = ref('idle');
|
||||
const dialogVisible = ref(false)
|
||||
const dialogVisible = ref(false);
|
||||
|
||||
const { logoUrl, productName } = storeToRefs(systemStore);
|
||||
const { headerBackgroundColor } = storeToRefs(themeStore);
|
||||
const { uid, avatar, username, unreadMsgCount } = storeToRefs(userStore);
|
||||
const { avatar, username } = storeToRefs(userStore);
|
||||
|
||||
|
||||
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) => {
|
||||
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(() => {
|
||||
refreshUnreadMsgCount();
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
|
|
|
@ -54,7 +54,7 @@ const { productName, collapse, sidebarWidth, flowSidebarVisible } = storeToRefs(
|
|||
|
||||
const { backgroundColor, sidebarBackgroundColor } = storeToRefs(themeStore);
|
||||
|
||||
const loading = ref(false)
|
||||
const loading = ref(true)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -87,7 +87,6 @@ provide('logout', logout)
|
|||
|
||||
onMounted(() => {
|
||||
document.title = productName.value
|
||||
let schema = window.location.protocol == 'http:' ? "ws://" : 'wss://'
|
||||
//更新用户信息
|
||||
getConfigure().then(res => {
|
||||
const { setAttributeValue } = useSystemStore();
|
||||
|
@ -95,6 +94,14 @@ onMounted(() => {
|
|||
let row = res[i];
|
||||
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 => {
|
||||
console.log(e);
|
||||
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,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">
|
||||
</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',
|
||||
logoUrl: '//s3.tebi.io/tenos/images/logo/jc.png',
|
||||
copyright: '2005-2023 JUSTCALL 版权 © 2023 集时股份呼叫中心开发团队',
|
||||
productName: '呼叫中心平台',
|
||||
productName: '在线系统',
|
||||
variables: {},
|
||||
}
|
||||
},
|
||||
|
|
|
@ -11,6 +11,7 @@ const useUserStore = defineStore('user', {
|
|||
pwdhash: '',
|
||||
accessToken: '*',
|
||||
tokenExpiredAt: 0,
|
||||
isAdmin: false,
|
||||
permissions: [],
|
||||
description: '',
|
||||
loginDevice: false,
|
||||
|
@ -56,6 +57,9 @@ const useUserStore = defineStore('user', {
|
|||
setDeviceRegisterState(ok) {
|
||||
this.deviceRegistered = ok
|
||||
},
|
||||
setIsAdmin(b) {
|
||||
this.isAdmin = b;
|
||||
},
|
||||
setPermissions(s) {
|
||||
if (typeof s === 'string') {
|
||||
try {
|
||||
|
@ -98,6 +102,9 @@ const useUserStore = defineStore('user', {
|
|||
},
|
||||
hasPermission(permission) {
|
||||
let ret = false
|
||||
if (this.isAdmin) {
|
||||
return true;
|
||||
}
|
||||
if (typeof permission === 'boolean') {
|
||||
ret = permission
|
||||
} else if (Array.isArray(permission)) {
|
||||
|
|
|
@ -64,8 +64,9 @@ const submit = () => {
|
|||
}
|
||||
loading.value = true;
|
||||
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);
|
||||
setIsAdmin(res.admin);
|
||||
setAccessToken(res.token, res.expire_in);
|
||||
setPassword(model.value.password);
|
||||
setAutoLoginDevice(model.value.remember);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<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 }">
|
||||
<el-tree ref="treeElement" :data="permissions" v-if="schema.column == 'permissions'" node-key="id"
|
||||
v-model="model.permissions" :props="treeOptions" @check-change="handleTreeChange(model)"
|
||||
|
|
|
@ -23,7 +23,8 @@
|
|||
<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 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>
|
||||
|
@ -31,8 +32,8 @@
|
|||
<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-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>
|
||||
|
@ -75,7 +76,7 @@
|
|||
box-shadow: var(--el-box-shadow-light);
|
||||
}
|
||||
|
||||
.profile-tabs{
|
||||
.profile-tabs {
|
||||
background-color: white;
|
||||
border-radius: 6px;
|
||||
box-shadow: var(--el-box-shadow-light);
|
||||
|
|
|
@ -15,4 +15,15 @@ export default defineConfig({
|
|||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
build: {
|
||||
outDir: 'release',
|
||||
assetsDir: 'static',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks(id) {
|
||||
return 'notebook'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue