mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	Merge branch 'ui' of 172.28.1.6:yangjian/chatgpt-plus into ui
This commit is contained in:
		@@ -146,6 +146,7 @@ func authorizeMiddleware(s *AppServer, client *redis.Client) gin.HandlerFunc {
 | 
			
		||||
		if c.Request.URL.Path == "/api/user/login" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/user/resetPass" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/admin/login" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/admin/login/captcha" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/user/register" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/chat/history" ||
 | 
			
		||||
			c.Request.URL.Path == "/api/chat/detail" ||
 | 
			
		||||
 
 | 
			
		||||
@@ -124,8 +124,10 @@ func (c RedisConfig) Url() string {
 | 
			
		||||
 | 
			
		||||
// Manager 管理员
 | 
			
		||||
type Manager struct {
 | 
			
		||||
	Username string `json:"username"`
 | 
			
		||||
	Password string `json:"password"`
 | 
			
		||||
	Username  string `json:"username"`
 | 
			
		||||
	Password  string `json:"password"`
 | 
			
		||||
	Captcha   string `json:"captcha"`    // 验证码
 | 
			
		||||
	CaptchaId string `json:"captcha_id"` // 验证码id
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ChatConfig 系统默认的聊天配置
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								api/go.mod
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								api/go.mod
									
									
									
									
									
								
							@@ -27,6 +27,16 @@ require github.com/xxl-job/xxl-job-executor-go v1.2.0
 | 
			
		||||
 | 
			
		||||
require github.com/bg5t/mydiscordgo v0.28.1
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/mojocn/base64Captcha v1.3.1
 | 
			
		||||
	github.com/shopspring/decimal v1.3.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 | 
			
		||||
	golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 // indirect
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	github.com/andybalholm/brotli v1.0.4 // indirect
 | 
			
		||||
	github.com/bytedance/sonic v1.9.1 // indirect
 | 
			
		||||
 
 | 
			
		||||
@@ -65,6 +65,8 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
 | 
			
		||||
github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
 | 
			
		||||
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
 | 
			
		||||
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/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
 | 
			
		||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
@@ -129,6 +131,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
 | 
			
		||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.1 h1:2Wbkt8Oc8qjmNJ5GyOfSo4tgVQPsbKMftqASnq8GlT0=
 | 
			
		||||
github.com/mojocn/base64Captcha v1.3.1/go.mod h1:wAQCKEc5bDujxKRmbT6/vTnTt5CjStQ8bRfPWUuz/iY=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
 | 
			
		||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
 | 
			
		||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 | 
			
		||||
@@ -166,6 +170,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
 | 
			
		||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
 | 
			
		||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 | 
			
		||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 | 
			
		||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 | 
			
		||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 | 
			
		||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
 | 
			
		||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 | 
			
		||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
 | 
			
		||||
@@ -227,6 +233,8 @@ golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
 | 
			
		||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
 | 
			
		||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
 | 
			
		||||
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 | 
			
		||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75 h1:TbGuee8sSq15Iguxu4deQ7+Bqq/d2rsQejGcEtADAMQ=
 | 
			
		||||
golang.org/x/image v0.0.0-20190501045829-6d32002ffd75/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 | 
			
		||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU=
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"github.com/go-redis/redis/v8"
 | 
			
		||||
	"github.com/golang-jwt/jwt/v5"
 | 
			
		||||
	"github.com/mojocn/base64Captcha"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
@@ -36,6 +37,13 @@ func (h *ManagerHandler) Login(c *gin.Context) {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// add captcha
 | 
			
		||||
	if !base64Captcha.DefaultMemStore.Verify(data.CaptchaId, data.Captcha, true) {
 | 
			
		||||
		resp.ERROR(c, "验证码错误,请重新输入!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	manager := h.App.Config.Manager
 | 
			
		||||
	if data.Username == manager.Username && data.Password == manager.Password {
 | 
			
		||||
		// 创建 token
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								api/handler/admin/admin_user_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								api/handler/admin/admin_user_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/core/types"
 | 
			
		||||
	"chatplus/handler"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SysUserHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
	db *gorm.DB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewSysUserHandler(app *core.AppServer, db *gorm.DB) *SysUserHandler {
 | 
			
		||||
	h := SysUserHandler{db: db}
 | 
			
		||||
	h.App = app
 | 
			
		||||
	return &h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List 用户列表
 | 
			
		||||
func (h *SysUserHandler) List(c *gin.Context) {
 | 
			
		||||
	page := h.GetInt(c, "page", 1)
 | 
			
		||||
	pageSize := h.GetInt(c, "page_size", 20)
 | 
			
		||||
	username := h.GetTrim(c, "username")
 | 
			
		||||
 | 
			
		||||
	offset := (page - 1) * pageSize
 | 
			
		||||
	var items []model.AdminUser
 | 
			
		||||
	var users = make([]vo.AdminUser, 0)
 | 
			
		||||
	var total int64
 | 
			
		||||
 | 
			
		||||
	session := h.db.Session(&gorm.Session{})
 | 
			
		||||
	if username != "" {
 | 
			
		||||
		session = session.Where("username LIKE ?", "%"+username+"%")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 查询total
 | 
			
		||||
	session.Model(&model.AdminUser{}).Count(&total)
 | 
			
		||||
	res := session.Offset(offset).Limit(pageSize).Find(&items)
 | 
			
		||||
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range items {
 | 
			
		||||
			var userVo vo.AdminUser
 | 
			
		||||
			err := utils.CopyObject(item, &userVo)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				userVo.Id = item.Id
 | 
			
		||||
				userVo.CreatedAt = item.CreatedAt.Unix()
 | 
			
		||||
				userVo.UpdatedAt = item.UpdatedAt.Unix()
 | 
			
		||||
				users = append(users, userVo)
 | 
			
		||||
			} else {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	pageVo := vo.NewPage(total, page, pageSize, users)
 | 
			
		||||
	resp.SUCCESS(c, pageVo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Save 更新或者新增
 | 
			
		||||
func (h *SysUserHandler) Save(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id       uint   `json:"id"`
 | 
			
		||||
		Password string `json:"password"`
 | 
			
		||||
		Username string `json:"username"`
 | 
			
		||||
		Status   bool   `json:"status"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var user = model.AdminUser{}
 | 
			
		||||
	var res *gorm.DB
 | 
			
		||||
	var userVo vo.AdminUser
 | 
			
		||||
	if data.Id > 0 { // 更新
 | 
			
		||||
		user.Id = data.Id
 | 
			
		||||
		// 此处需要用 map 更新,用结构体无法更新 0 值
 | 
			
		||||
		res = h.db.Model(&user).Updates(map[string]interface{}{
 | 
			
		||||
			"username": data.Username,
 | 
			
		||||
			"status":   data.Status,
 | 
			
		||||
		})
 | 
			
		||||
	} else {
 | 
			
		||||
		salt := utils.RandString(8)
 | 
			
		||||
		u := model.AdminUser{
 | 
			
		||||
			Username: data.Username,
 | 
			
		||||
			Password: utils.GenPassword(data.Password, salt),
 | 
			
		||||
			Salt:     salt,
 | 
			
		||||
			Status:   true,
 | 
			
		||||
		}
 | 
			
		||||
		res = h.db.Create(&u)
 | 
			
		||||
		_ = utils.CopyObject(u, &userVo)
 | 
			
		||||
		userVo.Id = u.Id
 | 
			
		||||
		userVo.CreatedAt = u.CreatedAt.Unix()
 | 
			
		||||
		userVo.UpdatedAt = u.UpdatedAt.Unix()
 | 
			
		||||
	}
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "更新数据库失败")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, userVo)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ResetPass 重置密码
 | 
			
		||||
func (h *SysUserHandler) ResetPass(c *gin.Context) {
 | 
			
		||||
	var data struct {
 | 
			
		||||
		Id       uint
 | 
			
		||||
		Password string
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var user model.AdminUser
 | 
			
		||||
	res := h.db.First(&user, data.Id)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "No user found")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	password := utils.GenPassword(data.Password, user.Salt)
 | 
			
		||||
	user.Password = password
 | 
			
		||||
	res = h.db.Updates(&user)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c)
 | 
			
		||||
	} else {
 | 
			
		||||
		resp.SUCCESS(c)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove 删除
 | 
			
		||||
func (h *SysUserHandler) Remove(c *gin.Context) {
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	if id > 0 {
 | 
			
		||||
		res := h.db.Where("id = ?", id).Delete(&model.AdminUser{})
 | 
			
		||||
		if res.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "删除失败")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										41
									
								
								api/handler/admin/captcha_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								api/handler/admin/captcha_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/handler"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/mojocn/base64Captcha"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type CaptchaHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewCaptchaHandler(app *core.AppServer) *CaptchaHandler {
 | 
			
		||||
	h := CaptchaHandler{}
 | 
			
		||||
	h.App = app
 | 
			
		||||
	return &h
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CaptchaVo struct {
 | 
			
		||||
	CaptchaId string `json:"captcha_id"`
 | 
			
		||||
	PicPath   string `json:"pic_path"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCaptcha 获取验证码
 | 
			
		||||
func (h *CaptchaHandler) GetCaptcha(c *gin.Context) {
 | 
			
		||||
	var captchaVo CaptchaVo
 | 
			
		||||
	driver := base64Captcha.NewDriverDigit(48, 130, 4, 0.4, 10)
 | 
			
		||||
	cp := base64Captcha.NewCaptcha(driver, base64Captcha.DefaultMemStore)
 | 
			
		||||
	// b64s是图片的base64编码
 | 
			
		||||
	id, b64s, err := cp.Generate()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, "生成验证码错误!")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	captchaVo.CaptchaId = id
 | 
			
		||||
	captchaVo.PicPath = b64s
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, captchaVo)
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,7 @@ import (
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/shopspring/decimal"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
@@ -23,10 +24,11 @@ func NewDashboardHandler(app *core.AppServer, db *gorm.DB) *DashboardHandler {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type statsVo struct {
 | 
			
		||||
	Users  int64   `json:"users"`
 | 
			
		||||
	Chats  int64   `json:"chats"`
 | 
			
		||||
	Tokens int     `json:"tokens"`
 | 
			
		||||
	Income float64 `json:"income"`
 | 
			
		||||
	Users  int64                         `json:"users"`
 | 
			
		||||
	Chats  int64                         `json:"chats"`
 | 
			
		||||
	Tokens int                           `json:"tokens"`
 | 
			
		||||
	Income float64                       `json:"income"`
 | 
			
		||||
	Chart  map[string]map[string]float64 `json:"chart"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *DashboardHandler) Stats(c *gin.Context) {
 | 
			
		||||
@@ -67,5 +69,52 @@ func (h *DashboardHandler) Stats(c *gin.Context) {
 | 
			
		||||
	for _, item := range orders {
 | 
			
		||||
		stats.Income += item.Amount
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计7天的订单的图表
 | 
			
		||||
	startDate := now.Add(-7 * 24 * time.Hour).Format("2006-01-02")
 | 
			
		||||
	var statsChart = make(map[string]map[string]float64)
 | 
			
		||||
	//// 初始化
 | 
			
		||||
	var userStatistic, historyMessagesStatistic, incomeStatistic = make(map[string]float64), make(map[string]float64), make(map[string]float64)
 | 
			
		||||
	for i := 0; i < 7; i++ {
 | 
			
		||||
		var initTime = time.Date(now.Year(), now.Month(), now.Day()-i, 0, 0, 0, 0, now.Location()).Format("2006-01-02")
 | 
			
		||||
		userStatistic[initTime] = float64(0)
 | 
			
		||||
		historyMessagesStatistic[initTime] = float64(0)
 | 
			
		||||
		incomeStatistic[initTime] = float64(0)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计用户7天增加的曲线
 | 
			
		||||
	var users []model.User
 | 
			
		||||
	res = h.db.Model(&model.User{}).Where("created_at > ?", startDate).Find(&users)
 | 
			
		||||
	if res.Error == nil {
 | 
			
		||||
		for _, item := range users {
 | 
			
		||||
			userStatistic[item.CreatedAt.Format("2006-01-02")] += 1
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计7天Token 消耗
 | 
			
		||||
	res = h.db.Where("created_at > ?", startDate).Find(&historyMessages)
 | 
			
		||||
	for _, item := range historyMessages {
 | 
			
		||||
		historyMessagesStatistic[item.CreatedAt.Format("2006-01-02")] += float64(item.Tokens)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 浮点数相加?
 | 
			
		||||
	// 统计最近7天的众筹
 | 
			
		||||
	res = h.db.Where("created_at > ?", startDate).Find(&rewards)
 | 
			
		||||
	for _, item := range rewards {
 | 
			
		||||
		incomeStatistic[item.CreatedAt.Format("2006-01-02")], _ = decimal.NewFromFloat(incomeStatistic[item.CreatedAt.Format("2006-01-02")]).Add(decimal.NewFromFloat(item.Amount)).Float64()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 统计最近7天的订单
 | 
			
		||||
	res = h.db.Where("status = ?", types.OrderPaidSuccess).Where("created_at > ?", startDate).Find(&orders)
 | 
			
		||||
	for _, item := range orders {
 | 
			
		||||
		incomeStatistic[item.CreatedAt.Format("2006-01-02")], _ = decimal.NewFromFloat(incomeStatistic[item.CreatedAt.Format("2006-01-02")]).Add(decimal.NewFromFloat(item.Amount)).Float64()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	statsChart["users"] = userStatistic
 | 
			
		||||
	statsChart["historyMessage"] = historyMessagesStatistic
 | 
			
		||||
	statsChart["orders"] = incomeStatistic
 | 
			
		||||
 | 
			
		||||
	stats.Chart = statsChart
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, stats)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								api/handler/admin/upload_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								api/handler/admin/upload_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"chatplus/core"
 | 
			
		||||
	"chatplus/handler"
 | 
			
		||||
	"chatplus/service/oss"
 | 
			
		||||
	"chatplus/store/model"
 | 
			
		||||
	"chatplus/store/vo"
 | 
			
		||||
	"chatplus/utils"
 | 
			
		||||
	"chatplus/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type UploadHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
	db              *gorm.DB
 | 
			
		||||
	uploaderManager *oss.UploaderManager
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewUploadHandler(app *core.AppServer, db *gorm.DB, manager *oss.UploaderManager) *UploadHandler {
 | 
			
		||||
	adminHandler := &UploadHandler{db: db, uploaderManager: manager}
 | 
			
		||||
	adminHandler.App = app
 | 
			
		||||
	return adminHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *UploadHandler) Upload(c *gin.Context) {
 | 
			
		||||
	file, err := h.uploaderManager.GetUploadHandler().PutFile(c, "file")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	userId := 0
 | 
			
		||||
	res := h.db.Create(&model.File{
 | 
			
		||||
		UserId:    userId,
 | 
			
		||||
		Name:      file.Name,
 | 
			
		||||
		ObjKey:    file.ObjKey,
 | 
			
		||||
		URL:       file.URL,
 | 
			
		||||
		Ext:       file.Ext,
 | 
			
		||||
		Size:      file.Size,
 | 
			
		||||
		CreatedAt: time.Time{},
 | 
			
		||||
	})
 | 
			
		||||
	if res.Error != nil || res.RowsAffected == 0 {
 | 
			
		||||
		resp.ERROR(c, "error with update database: "+res.Error.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, file)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (h *UploadHandler) List(c *gin.Context) {
 | 
			
		||||
	userId := 0
 | 
			
		||||
	var items []model.File
 | 
			
		||||
	var files = make([]vo.File, 0)
 | 
			
		||||
	h.db.Where("user_id = ?", userId).Find(&items)
 | 
			
		||||
	if len(items) > 0 {
 | 
			
		||||
		for _, v := range items {
 | 
			
		||||
			var file vo.File
 | 
			
		||||
			err := utils.CopyObject(v, &file)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.Error(err)
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			file.CreatedAt = v.CreatedAt.Unix()
 | 
			
		||||
			files = append(files, file)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, files)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Remove remove files
 | 
			
		||||
func (h *UploadHandler) Remove(c *gin.Context) {
 | 
			
		||||
	userId := 0
 | 
			
		||||
	id := h.GetInt(c, "id", 0)
 | 
			
		||||
	var file model.File
 | 
			
		||||
	tx := h.db.Where("user_id = ? AND id = ?", userId, id).First(&file)
 | 
			
		||||
	if tx.Error != nil || file.Id == 0 {
 | 
			
		||||
		resp.ERROR(c, "file not existed")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// remove database
 | 
			
		||||
	tx = h.db.Model(&model.File{}).Delete("id = ?", id)
 | 
			
		||||
	if tx.Error != nil || tx.RowsAffected == 0 {
 | 
			
		||||
		resp.ERROR(c, "failed to update database")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// remove files
 | 
			
		||||
	objectKey := file.ObjKey
 | 
			
		||||
	if objectKey == "" {
 | 
			
		||||
		objectKey = file.URL
 | 
			
		||||
	}
 | 
			
		||||
	_ = h.uploaderManager.GetUploadHandler().Delete(objectKey)
 | 
			
		||||
	resp.SUCCESS(c)
 | 
			
		||||
}
 | 
			
		||||
@@ -124,10 +124,9 @@ func (h *ChatHandler) Clear(c *gin.Context) {
 | 
			
		||||
// History 获取聊天历史记录
 | 
			
		||||
func (h *ChatHandler) History(c *gin.Context) {
 | 
			
		||||
	chatId := c.Query("chat_id") // 会话 ID
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	var items []model.ChatMessage
 | 
			
		||||
	var messages = make([]vo.HistoryMessage, 0)
 | 
			
		||||
	res := h.db.Where("user_id = ? AND chat_id = ?", userId, chatId).Find(&items)
 | 
			
		||||
	res := h.db.Debug().Where("chat_id = ?", chatId).Find(&items)
 | 
			
		||||
	if res.Error != nil {
 | 
			
		||||
		resp.ERROR(c, "No history message")
 | 
			
		||||
		return
 | 
			
		||||
 
 | 
			
		||||
@@ -276,6 +276,7 @@ func (h *ChatHandler) sendOpenAiMessage(
 | 
			
		||||
		var res types.ApiError
 | 
			
		||||
		err = json.Unmarshal(body, &res)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Debug(string(body))
 | 
			
		||||
			return fmt.Errorf("error with decode response: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ func (h *UploadHandler) Upload(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
	userId := h.GetLoginUserId(c)
 | 
			
		||||
	res := h.db.Create(&model.File{
 | 
			
		||||
		UserId:    userId,
 | 
			
		||||
		UserId:    int(userId),
 | 
			
		||||
		Name:      file.Name,
 | 
			
		||||
		ObjKey:    file.ObjKey,
 | 
			
		||||
		URL:       file.URL,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								api/main.go
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								api/main.go
									
									
									
									
									
								
							@@ -369,6 +369,30 @@ func main() {
 | 
			
		||||
			group.GET("token", h.GenToken)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		// 验证码
 | 
			
		||||
		fx.Provide(admin.NewCaptchaHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.CaptchaHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/admin/login/")
 | 
			
		||||
			group.GET("captcha", h.GetCaptcha)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		fx.Provide(admin.NewUploadHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.UploadHandler) {
 | 
			
		||||
			s.Engine.POST("/api/admin/upload", h.Upload)
 | 
			
		||||
			s.Engine.GET("/api/admin/upload/list", h.List)
 | 
			
		||||
			s.Engine.GET("/api/admin/upload/remove", h.Remove)
 | 
			
		||||
		}),
 | 
			
		||||
 | 
			
		||||
		//// 系统管理员
 | 
			
		||||
		//fx.Provide(admin.NewSysUserHandler),
 | 
			
		||||
		//fx.Invoke(func(s *core.AppServer, h *admin.SysUserHandler) {
 | 
			
		||||
		//	group := s.Engine.Group("/api/admin/sysUser/")
 | 
			
		||||
		//	group.POST("save", h.Save)
 | 
			
		||||
		//	group.GET("list", h.List)
 | 
			
		||||
		//	group.POST("remove", h.Remove)
 | 
			
		||||
		//	group.POST("resetPass", h.ResetPass)
 | 
			
		||||
		//}),
 | 
			
		||||
 | 
			
		||||
		fx.Provide(handler.NewFunctionHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *handler.FunctionHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/function/")
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										11
									
								
								api/store/model/admin_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								api/store/model/admin_user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
			
		||||
package model
 | 
			
		||||
 | 
			
		||||
type AdminUser struct {
 | 
			
		||||
	BaseModel
 | 
			
		||||
	Username    string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Salt        string // 密码盐
 | 
			
		||||
	Status      bool   `gorm:"default:true"` // 当前状态
 | 
			
		||||
	LastLoginAt int64  // 最后登录时间
 | 
			
		||||
	LastLoginIp string // 最后登录 IP
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,7 @@ import "time"
 | 
			
		||||
 | 
			
		||||
type File struct {
 | 
			
		||||
	Id        uint `gorm:"primarykey;column:id"`
 | 
			
		||||
	UserId    uint
 | 
			
		||||
	UserId    int
 | 
			
		||||
	Name      string
 | 
			
		||||
	ObjKey    string
 | 
			
		||||
	URL       string
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								api/store/vo/admin_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								api/store/vo/admin_user.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
type AdminUser struct {
 | 
			
		||||
	BaseVo
 | 
			
		||||
	Username    string `json:"username"`
 | 
			
		||||
	Salt        string `json:"salt"`          // 密码盐
 | 
			
		||||
	Status      bool   `json:"status"`        // 当前状态
 | 
			
		||||
	LastLoginAt int64  `json:"last_login_at"` // 最后登录时间
 | 
			
		||||
	LastLoginIp string `json:"last_login_ip"` // 最后登录 IP
 | 
			
		||||
}
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
VUE_APP_API_HOST=http://localhost:5678
 | 
			
		||||
VUE_APP_WS_HOST=ws://localhost:5678
 | 
			
		||||
VUE_APP_API_HOST=http://172.22.11.2:5678
 | 
			
		||||
VUE_APP_WS_HOST=ws://172.22.11.2:5678
 | 
			
		||||
VUE_APP_USER=18575670125
 | 
			
		||||
VUE_APP_PASS=12345678
 | 
			
		||||
VUE_APP_ADMIN_USER=admin
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7627
									
								
								web/pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										7627
									
								
								web/pnpm-lock.yaml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -51,6 +51,10 @@
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 20px;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .text-line .align-right {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: right;
 | 
			
		||||
}
 | 
			
		||||
.mobile-mj .content .running-job-list .van-grid .van-grid-item .van-grid-item__content {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
 
 | 
			
		||||
@@ -67,6 +67,11 @@
 | 
			
		||||
          right 20px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .align-right {
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content right
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .running-job-list {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,6 @@
 | 
			
		||||
.task-list-box .task-list-inner .title-tabs .el-tabs__active-bar {
 | 
			
		||||
  background-color: #47fff1;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .title-tabs .el-tabs__content {
 | 
			
		||||
  padding: 10px 0;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .el-textarea {
 | 
			
		||||
  --el-input-focus-border-color: #47fff1;
 | 
			
		||||
}
 | 
			
		||||
@@ -51,26 +48,49 @@
 | 
			
		||||
.task-list-box .task-list-inner .el-form-item__label {
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-uploader .el-upload {
 | 
			
		||||
.task-list-box .task-list-inner .img-inline {
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-uploader .el-upload {
 | 
			
		||||
  border: 1px dashed var(--el-border-color);
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  width: 300px;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
  transition: var(--el-transition-duration-fast);
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-uploader .el-upload:hover {
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-uploader .el-upload:hover {
 | 
			
		||||
  border-color: var(--el-color-primary);
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-uploader .el-upload .el-icon.uploader-icon {
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-uploader .el-upload .el-icon.uploader-icon {
 | 
			
		||||
  font-size: 28px;
 | 
			
		||||
  color: #8c939d;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 120px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-list-box {
 | 
			
		||||
  display: flex;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-list-box .img-item {
 | 
			
		||||
  width: 120px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  margin-right: 10px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-image {
 | 
			
		||||
  width: 120px;
 | 
			
		||||
  height: 120px;
 | 
			
		||||
  border-radius: 5px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .img-inline .img-list-box .img-item .el-button {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 5px;
 | 
			
		||||
  top: 5px;
 | 
			
		||||
  width: 20px;
 | 
			
		||||
  height: 20px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .submit-btn {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  margin: 20px 0;
 | 
			
		||||
@@ -84,17 +104,17 @@
 | 
			
		||||
  justify-content: right;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .running-job-list .job-item {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  padding: 2px;
 | 
			
		||||
  background-color: #555;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .running-job-list .job-item .job-item-inner {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .running-job-list .job-item .job-item-inner .progress {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
@@ -104,11 +124,11 @@
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .running-job-list .job-item .job-item-inner .progress span {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .running-job-list .job-item .job-item-inner .progress span {
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  border: 1px solid #666;
 | 
			
		||||
@@ -116,18 +136,19 @@
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  border-radius: 6px;
 | 
			
		||||
  transition: all 0.3s ease; /* 添加过渡效果 */
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item .opt .opt-line {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line {
 | 
			
		||||
  margin: 6px 0;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: row;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul li {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li {
 | 
			
		||||
  margin-right: 6px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a {
 | 
			
		||||
  padding: 3px 0;
 | 
			
		||||
  width: 40px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
@@ -137,50 +158,58 @@
 | 
			
		||||
  background-color: #4e5058;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul li a:hover {
 | 
			
		||||
  background-color: #6d6f78;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .opt .opt-line ul .show-prompt {
 | 
			
		||||
  font-size: 20px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .finish-job-list .animate:hover {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item .remove {
 | 
			
		||||
  display: none;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  right: 10px;
 | 
			
		||||
  top: 10px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .job-item:hover .remove {
 | 
			
		||||
  display: block;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .finish-job-list .animate:hover {
 | 
			
		||||
  box-shadow: 0 0 10px rgba(71,255,241,0.6); /* 添加阴影效果 */
 | 
			
		||||
  transform: translateY(-10px); /* 向上移动10像素 */
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  overflow: visible;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image img {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image img {
 | 
			
		||||
  height: 240px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image .el-image-viewer__wrapper img {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image .el-image-viewer__wrapper img {
 | 
			
		||||
  width: auto;
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image .image-slot {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image .image-slot {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: column;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  min-height: 200px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  height: 240px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image .image-slot .iconfont {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image .image-slot .iconfont {
 | 
			
		||||
  font-size: 50px;
 | 
			
		||||
  margin-bottom: 10px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image.upscale {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image.upscale {
 | 
			
		||||
  max-height: 310px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image.upscale img {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image.upscale img {
 | 
			
		||||
  height: 310px;
 | 
			
		||||
}
 | 
			
		||||
.task-list-box .el-image.upscale .el-image-viewer__wrapper img {
 | 
			
		||||
.task-list-box .task-list-inner .job-list-box .el-image.upscale .el-image-viewer__wrapper img {
 | 
			
		||||
  width: auto;
 | 
			
		||||
  height: auto;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -87,8 +87,8 @@ const handleKeyup = (e) => {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const login = function () {
 | 
			
		||||
  if (!validateMobile(username.value) && !validateEmail(username.value)) {
 | 
			
		||||
    return ElMessage.error("请输入合法的手机号/邮箱地址")
 | 
			
		||||
  if (username.value.trim() === '') {
 | 
			
		||||
    return ElMessage.error("请输入用户民")
 | 
			
		||||
  }
 | 
			
		||||
  if (password.value.trim() === '') {
 | 
			
		||||
    return ElMessage.error('请输入密码');
 | 
			
		||||
 
 | 
			
		||||
@@ -62,14 +62,18 @@
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div class="text-line">
 | 
			
		||||
          <van-field
 | 
			
		||||
              v-model="params.prompt"
 | 
			
		||||
              rows="3"
 | 
			
		||||
              autosize
 | 
			
		||||
              label="提示词"
 | 
			
		||||
              type="textarea"
 | 
			
		||||
              placeholder="如:一个美丽的中国女孩站在电影院门口,手上拿着爆米花,微笑,写实风格,电影灯光效果,半身像"
 | 
			
		||||
          />
 | 
			
		||||
          <van-field v-model="params.prompt"
 | 
			
		||||
                     rows="3"
 | 
			
		||||
                     label="提示词"
 | 
			
		||||
                     autosize
 | 
			
		||||
                     type="textarea"
 | 
			
		||||
                     placeholder="如:一个美丽的中国女孩站在电影院门口,手上拿着爆米花,微笑,写实风格,电影灯光效果,半身像">
 | 
			
		||||
            <template #button>
 | 
			
		||||
              <van-button v-if="translating" disabled loading type="primary"/>
 | 
			
		||||
              <van-button v-else size="small" type="primary" @click="translatePrompt">翻译</van-button>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
          </van-field>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <van-collapse v-model="activeColspan">
 | 
			
		||||
@@ -192,7 +196,15 @@
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {showConfirmDialog, showFailToast, showNotify, showToast, showDialog, showImagePreview} from "vant";
 | 
			
		||||
import {
 | 
			
		||||
  showConfirmDialog,
 | 
			
		||||
  showFailToast,
 | 
			
		||||
  showNotify,
 | 
			
		||||
  showToast,
 | 
			
		||||
  showDialog,
 | 
			
		||||
  showImagePreview,
 | 
			
		||||
  showSuccessToast
 | 
			
		||||
} from "vant";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import Compressor from "compressorjs";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
@@ -449,10 +461,10 @@ const publishImage = (item, action) => {
 | 
			
		||||
    text = "取消发布"
 | 
			
		||||
  }
 | 
			
		||||
  httpPost("/api/mj/publish", {id: item.id, action: action}).then(() => {
 | 
			
		||||
    ElMessage.success(text + "成功")
 | 
			
		||||
    showSuccessToast(text + "成功")
 | 
			
		||||
    item.publish = action
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error(text + "失败:" + e.message)
 | 
			
		||||
    showFailToast(text + "失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -469,6 +481,23 @@ const imageView = (item) => {
 | 
			
		||||
  showImagePreview([item['img_url']]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const translating = ref(false)
 | 
			
		||||
const translatePrompt = () => {
 | 
			
		||||
  if (params.value.prompt === '') {
 | 
			
		||||
    return showToast("请输入中文提示词!")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  translating.value = true
 | 
			
		||||
  const prompt = params.value.prompt
 | 
			
		||||
  httpPost("/api/prompt/translate", {"prompt": prompt}).then(res => {
 | 
			
		||||
    params.value.prompt = res.data
 | 
			
		||||
    translating.value = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    translating.value = false
 | 
			
		||||
    showFailToast("翻译失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus">
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user