Merge branch 'main' into dev
@ -73,7 +73,7 @@ ChatGLM,讯飞星火,文心一言等多个平台的大语言模型。集成了
|
||||
**演示站不提供任何充值点卡售卖或者VIP充值服务。** 如果您体验过后觉得还不错的话,可以花两分钟用下面的一键部署脚本自己部署一套。
|
||||
|
||||
```shell
|
||||
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v3.2.7-6c232bdaf8.sh)"
|
||||
bash -c "$(curl -fsSL https://img.r9it.com/tmp/install-v4.0.1-5f1a7c3fc9.sh)"
|
||||
```
|
||||
|
||||
最新版本的一键部署脚本请参考 [**ChatGPT-Plus 文档**](https://ai.r9it.com/docs/install/)。
|
||||
|
@ -217,6 +217,7 @@ func needLogin(c *gin.Context) bool {
|
||||
c.Request.URL.Path == "/api/sd/client" ||
|
||||
c.Request.URL.Path == "/api/config/get" ||
|
||||
c.Request.URL.Path == "/api/product/list" ||
|
||||
c.Request.URL.Path == "/api/menu/list" ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/test") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/function/") ||
|
||||
strings.HasPrefix(c.Request.URL.Path, "/api/sms/") ||
|
||||
|
@ -144,13 +144,13 @@ type SystemConfig struct {
|
||||
PowerPrice float64 `json:"power_price,omitempty"` // 算力单价
|
||||
|
||||
OrderPayTimeout int `json:"order_pay_timeout,omitempty"` //订单支付超时时间
|
||||
VipInfoText string `json:"vip_info_text"` // 会员页面充值说明
|
||||
VipInfoText string `json:"vip_info_text,omitempty"` // 会员页面充值说明
|
||||
DefaultModels []int `json:"default_models,omitempty"` // 默认开通的 AI 模型
|
||||
|
||||
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
|
||||
MjActionPower int `json:"mj_action_power"` // MJ 操作(放大,变换)消耗算力
|
||||
SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力
|
||||
DallPower int `json:"dall_power,omitempty"` // DALLE3 绘图消耗算力
|
||||
MjPower int `json:"mj_power,omitempty"` // MJ 绘画消耗算力
|
||||
MjActionPower int `json:"mj_action_power,omitempty"` // MJ 操作(放大,变换)消耗算力
|
||||
SdPower int `json:"sd_power,omitempty"` // SD 绘画消耗算力
|
||||
DallPower int `json:"dall_power,omitempty"` // DALLE3 绘图消耗算力
|
||||
|
||||
WechatCardURL string `json:"wechat_card_url,omitempty"` // 微信客服地址
|
||||
|
||||
|
121
api/handler/admin/menu_handler.go
Normal file
@ -0,0 +1,121 @@
|
||||
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 MenuHandler struct {
|
||||
handler.BaseHandler
|
||||
}
|
||||
|
||||
func NewMenuHandler(app *core.AppServer, db *gorm.DB) *MenuHandler {
|
||||
return &MenuHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
|
||||
}
|
||||
|
||||
func (h *MenuHandler) Save(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url"`
|
||||
SortNum int `json:"sort_num"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
res := h.DB.Save(&model.Menu{
|
||||
Id: data.Id,
|
||||
Name: data.Name,
|
||||
Icon: data.Icon,
|
||||
URL: data.URL,
|
||||
SortNum: data.SortNum,
|
||||
Enabled: data.Enabled,
|
||||
})
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败!")
|
||||
return
|
||||
}
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
// List 数据列表
|
||||
func (h *MenuHandler) List(c *gin.Context) {
|
||||
var items []model.Menu
|
||||
var list = make([]vo.Menu, 0)
|
||||
res := h.DB.Order("sort_num ASC").Find(&items)
|
||||
if res.Error == nil {
|
||||
for _, item := range items {
|
||||
var product vo.Menu
|
||||
err := utils.CopyObject(item, &product)
|
||||
if err == nil {
|
||||
list = append(list, product)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.SUCCESS(c, list)
|
||||
}
|
||||
|
||||
func (h *MenuHandler) Enable(c *gin.Context) {
|
||||
var data struct {
|
||||
Id uint `json:"id"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
res := h.DB.Model(&model.Menu{}).Where("id", data.Id).UpdateColumn("enabled", data.Enabled)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败!")
|
||||
return
|
||||
}
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
func (h *MenuHandler) Sort(c *gin.Context) {
|
||||
var data struct {
|
||||
Ids []uint `json:"ids"`
|
||||
Sorts []int `json:"sorts"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&data); err != nil {
|
||||
resp.ERROR(c, types.InvalidArgs)
|
||||
return
|
||||
}
|
||||
|
||||
for index, id := range data.Ids {
|
||||
res := h.DB.Model(&model.Menu{}).Where("id", id).Update("sort_num", data.Sorts[index])
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败!")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
resp.SUCCESS(c)
|
||||
}
|
||||
|
||||
func (h *MenuHandler) Remove(c *gin.Context) {
|
||||
id := h.GetInt(c, "id", 0)
|
||||
|
||||
if id > 0 {
|
||||
res := h.DB.Where("id", id).Delete(&model.Menu{})
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败!")
|
||||
return
|
||||
}
|
||||
}
|
||||
resp.SUCCESS(c)
|
||||
}
|
@ -65,21 +65,11 @@ func (h *ProductHandler) Save(c *gin.Context) {
|
||||
resp.SUCCESS(c, itemVo)
|
||||
}
|
||||
|
||||
// List 模型列表
|
||||
// List 数据列表
|
||||
func (h *ProductHandler) List(c *gin.Context) {
|
||||
if err := utils.CheckPermission(c, h.DB); err != nil {
|
||||
resp.NotPermission(c)
|
||||
return
|
||||
}
|
||||
|
||||
session := h.DB.Session(&gorm.Session{})
|
||||
enable := h.GetBool(c, "enable")
|
||||
if enable {
|
||||
session = session.Where("enabled", enable)
|
||||
}
|
||||
var items []model.Product
|
||||
var list = make([]vo.Product, 0)
|
||||
res := session.Order("sort_num ASC").Find(&items)
|
||||
res := h.DB.Order("sort_num ASC").Find(&items)
|
||||
if res.Error == nil {
|
||||
for _, item := range items {
|
||||
var product vo.Product
|
||||
@ -128,7 +118,7 @@ func (h *ProductHandler) Sort(c *gin.Context) {
|
||||
}
|
||||
|
||||
for index, id := range data.Ids {
|
||||
res := h.DB.Model(&model.Product{}).Where("id = ?", id).Update("sort_num", data.Sorts[index])
|
||||
res := h.DB.Model(&model.Product{}).Where("id", id).Update("sort_num", data.Sorts[index])
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败!")
|
||||
return
|
||||
@ -142,7 +132,7 @@ func (h *ProductHandler) Remove(c *gin.Context) {
|
||||
id := h.GetInt(c, "id", 0)
|
||||
|
||||
if id > 0 {
|
||||
res := h.DB.Where("id = ?", id).Delete(&model.Product{})
|
||||
res := h.DB.Where("id", id).Delete(&model.Product{})
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "更新数据库失败!")
|
||||
return
|
||||
|
@ -25,9 +25,10 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
|
||||
all := h.GetBool(c, "all")
|
||||
userId := h.GetLoginUserId(c)
|
||||
var roles []model.ChatRole
|
||||
var roleVos = make([]vo.ChatRole, 0)
|
||||
res := h.DB.Where("enable", true).Order("sort_num ASC").Find(&roles)
|
||||
if res.Error != nil {
|
||||
resp.ERROR(c, "No roles found,"+res.Error.Error())
|
||||
resp.SUCCESS(c, roleVos)
|
||||
return
|
||||
}
|
||||
|
||||
@ -55,8 +56,7 @@ func (h *ChatRoleHandler) List(c *gin.Context) {
|
||||
resp.ERROR(c, "角色解析失败!")
|
||||
return
|
||||
}
|
||||
// 转成 vo
|
||||
var roleVos = make([]vo.ChatRole, 0)
|
||||
|
||||
for _, r := range roles {
|
||||
if !utils.ContainsStr(roleKeys, r.Key) {
|
||||
continue
|
||||
|
36
api/handler/menu_handler.go
Normal file
@ -0,0 +1,36 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"chatplus/core"
|
||||
"chatplus/store/model"
|
||||
"chatplus/store/vo"
|
||||
"chatplus/utils"
|
||||
"chatplus/utils/resp"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MenuHandler struct {
|
||||
BaseHandler
|
||||
}
|
||||
|
||||
func NewMenuHandler(app *core.AppServer, db *gorm.DB) *MenuHandler {
|
||||
return &MenuHandler{BaseHandler: BaseHandler{App: app, DB: db}}
|
||||
}
|
||||
|
||||
// List 数据列表
|
||||
func (h *MenuHandler) List(c *gin.Context) {
|
||||
var items []model.Menu
|
||||
var list = make([]vo.Menu, 0)
|
||||
res := h.DB.Where("enabled", true).Order("sort_num ASC").Find(&items)
|
||||
if res.Error == nil {
|
||||
for _, item := range items {
|
||||
var product vo.Menu
|
||||
err := utils.CopyObject(item, &product)
|
||||
if err == nil {
|
||||
list = append(list, product)
|
||||
}
|
||||
}
|
||||
}
|
||||
resp.SUCCESS(c, list)
|
||||
}
|
@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"chatplus/core"
|
||||
"chatplus/core/types"
|
||||
"chatplus/service"
|
||||
"chatplus/service/oss"
|
||||
"chatplus/service/sd"
|
||||
"chatplus/store/model"
|
||||
@ -23,15 +24,17 @@ import (
|
||||
|
||||
type SdJobHandler struct {
|
||||
BaseHandler
|
||||
redis *redis.Client
|
||||
pool *sd.ServicePool
|
||||
uploader *oss.UploaderManager
|
||||
redis *redis.Client
|
||||
pool *sd.ServicePool
|
||||
uploader *oss.UploaderManager
|
||||
snowflake *service.Snowflake
|
||||
}
|
||||
|
||||
func NewSdJobHandler(app *core.AppServer, db *gorm.DB, pool *sd.ServicePool, manager *oss.UploaderManager) *SdJobHandler {
|
||||
func NewSdJobHandler(app *core.AppServer, db *gorm.DB, pool *sd.ServicePool, manager *oss.UploaderManager, snowflake *service.Snowflake) *SdJobHandler {
|
||||
return &SdJobHandler{
|
||||
pool: pool,
|
||||
uploader: manager,
|
||||
pool: pool,
|
||||
uploader: manager,
|
||||
snowflake: snowflake,
|
||||
BaseHandler: BaseHandler{
|
||||
App: app,
|
||||
DB: db,
|
||||
@ -116,8 +119,13 @@ func (h *SdJobHandler) Image(c *gin.Context) {
|
||||
}
|
||||
idValue, _ := c.Get(types.LoginUserID)
|
||||
userId := utils.IntValue(utils.InterfaceToString(idValue), 0)
|
||||
taskId, err := h.snowflake.Next(true)
|
||||
if err != nil {
|
||||
resp.ERROR(c, "error with generate task id: "+err.Error())
|
||||
return
|
||||
}
|
||||
params := types.SdTaskParams{
|
||||
TaskId: fmt.Sprintf("task(%s)", utils.RandString(15)),
|
||||
TaskId: taskId,
|
||||
Prompt: data.Prompt,
|
||||
NegativePrompt: data.NegativePrompt,
|
||||
Steps: data.Steps,
|
||||
|
14
api/main.go
@ -417,6 +417,20 @@ func main() {
|
||||
group := s.Engine.Group("/api/admin/powerLog/")
|
||||
group.POST("list", h.List)
|
||||
}),
|
||||
fx.Provide(admin.NewMenuHandler),
|
||||
fx.Invoke(func(s *core.AppServer, h *admin.MenuHandler) {
|
||||
group := s.Engine.Group("/api/admin/menu/")
|
||||
group.POST("save", h.Save)
|
||||
group.GET("list", h.List)
|
||||
group.POST("enable", h.Enable)
|
||||
group.POST("sort", h.Sort)
|
||||
group.GET("remove", h.Remove)
|
||||
}),
|
||||
fx.Provide(handler.NewMenuHandler),
|
||||
fx.Invoke(func(s *core.AppServer, h *handler.MenuHandler) {
|
||||
group := s.Engine.Group("/api/menu/")
|
||||
group.GET("list", h.List)
|
||||
}),
|
||||
fx.Invoke(func(s *core.AppServer, db *gorm.DB) {
|
||||
err := s.Run(db)
|
||||
if err != nil {
|
||||
|
@ -181,6 +181,7 @@ func (p *ServicePool) SyncTaskProgress() {
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if servicePlus := p.getService(job.ChannelId); servicePlus != nil {
|
||||
|
@ -77,6 +77,13 @@ func (s *Service) Run() {
|
||||
}
|
||||
}
|
||||
|
||||
var job model.MidJourneyJob
|
||||
tx := s.db.Where("id = ?", task.Id).First(&job)
|
||||
if tx.Error != nil {
|
||||
logger.Error("任务不存在,任务ID:", task.TaskId)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Infof("%s handle a new MidJourney task: %+v", s.Name, task)
|
||||
var res ImageRes
|
||||
switch task.Type {
|
||||
@ -97,8 +104,6 @@ func (s *Service) Run() {
|
||||
break
|
||||
}
|
||||
|
||||
var job model.MidJourneyJob
|
||||
s.db.Where("id = ?", task.Id).First(&job)
|
||||
if err != nil || (res.Code != 1 && res.Code != 22) {
|
||||
errMsg := fmt.Sprintf("%v,%s", err, res.Description)
|
||||
logger.Error("绘画任务执行失败:", errMsg)
|
||||
@ -127,14 +132,17 @@ func (s *Service) canHandleTask() bool {
|
||||
return handledNum < s.maxHandleTaskNum
|
||||
}
|
||||
|
||||
// remove the expired tasks
|
||||
// remove the timeout tasks
|
||||
func (s *Service) checkTasks() {
|
||||
for k, t := range s.taskStartTimes {
|
||||
if time.Now().Unix()-t.Unix() > s.taskTimeout {
|
||||
delete(s.taskStartTimes, k)
|
||||
atomic.AddInt32(&s.HandledTaskNum, -1)
|
||||
// delete task from database
|
||||
s.db.Delete(&model.MidJourneyJob{Id: uint(k)}, "progress < 100")
|
||||
|
||||
s.db.Model(&model.MidJourneyJob{Id: uint(k)}).UpdateColumns(map[string]interface{}{
|
||||
"progress": -1,
|
||||
"err_msg": "任务超时",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +114,7 @@ func (s *Service) Txt2Img(task types.SdTask) error {
|
||||
Width: task.Params.Width,
|
||||
Height: task.Params.Height,
|
||||
SamplerName: task.Params.Sampler,
|
||||
ForceTaskId: task.Params.TaskId,
|
||||
}
|
||||
if task.Params.Seed > 0 {
|
||||
body.Seed = task.Params.Seed
|
||||
|
11
api/store/model/menu.go
Normal file
@ -0,0 +1,11 @@
|
||||
package model
|
||||
|
||||
// Menu 系统菜单
|
||||
type Menu struct {
|
||||
Id uint `gorm:"primarykey;column:id"`
|
||||
Name string // 菜单名称
|
||||
Icon string // 菜单图标
|
||||
URL string // 菜单跳转地址
|
||||
SortNum int // 排序
|
||||
Enabled bool // 启用状态
|
||||
}
|
11
api/store/vo/menu.go
Normal file
@ -0,0 +1,11 @@
|
||||
package vo
|
||||
|
||||
// Menu 系统菜单
|
||||
type Menu struct {
|
||||
Id uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
URL string `json:"url"`
|
||||
SortNum int `json:"sort_num"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
@ -1,11 +1,5 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"chatplus/utils"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
text := "一只 蜗牛在树干上爬,阳光透过树叶照在蜗牛的背上 --ar 1:1 --iw 0.250000 --v 6"
|
||||
fmt.Println(utils.HasChinese(text))
|
||||
|
||||
}
|
||||
|
19
database/update-v4.0.2.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- 菜单表
|
||||
CREATE TABLE `chatgpt_plus`.`chatgpt_menus` (
|
||||
`id` INT(11) NOT NULL AUTO_INCREMENT ,
|
||||
`name` VARCHAR(30) NOT NULL COMMENT '菜单名称' ,
|
||||
`icon` VARCHAR(150) NOT NULL COMMENT '菜单图标' ,
|
||||
`url` VARCHAR(100) NOT NULL COMMENT '地址' ,
|
||||
`sort_num` SMALLINT(3) NOT NULL COMMENT '排序' ,
|
||||
`enabled` TINYINT(1) NOT NULL COMMENT '是否启用' ,
|
||||
PRIMARY KEY (`id`)) ENGINE = InnoDB COMMENT = '前端菜单表';
|
||||
|
||||
INSERT INTO `chatgpt_menus` (`id`, `name`, `icon`, `url`, `sort_num`, `enabled`) VALUES
|
||||
(1, '对话聊天', '/images/menu/chat.png', '/chat', 0, 1),
|
||||
(5, 'MJ 绘画', '/images/menu/mj.png', '/mj', 1, 1),
|
||||
(6, 'SD 绘画', '/images/menu/sd.png', '/sd', 2, 1),
|
||||
(7, '算力日志', '/images/menu/log.png', '/powerLog', 5, 1),
|
||||
(8, '应用中心', '/images/menu/app.png', '/apps', 3, 1),
|
||||
(9, '作品展示', '/images/menu/img-wall.png', '/images-wall', 4, 1),
|
||||
(10, '会员计划', '/images/menu/member.png', '/member', 6, 1),
|
||||
(11, '分享计划', '/images/menu/share.png', '/invite', 7, 1);
|
@ -3,7 +3,7 @@
|
||||
-- https://www.phpmyadmin.net/
|
||||
--
|
||||
-- 主机: localhost:3307
|
||||
-- 生成日期: 2024-03-28 17:27:20
|
||||
-- 生成日期: 2024-03-29 17:26:02
|
||||
-- 服务器版本: 8.0.33
|
||||
-- PHP 版本: 8.1.18
|
||||
|
||||
@ -47,7 +47,7 @@ CREATE TABLE `chatgpt_admin_users` (
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_admin_users` (`id`, `username`, `password`, `salt`, `status`, `last_login_at`, `last_login_ip`, `created_at`, `updated_at`) VALUES
|
||||
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1711594316, '172.22.11.200', '2024-03-11 16:30:20', '2024-03-28 10:51:57'),
|
||||
(1, 'admin', '6d17e80c87d209efb84ca4b2e0824f549d09fac8b2e1cc698de5bb5e1d75dfd0', 'mmrql75o', 1, 1711704342, '::1', '2024-03-11 16:30:20', '2024-03-29 17:25:42'),
|
||||
(108, 'test', '9ed720ce03e0a69885455271b4b3e1710bff79434f2a95d0de6406dd88cc9f79', '4b9orqjh', 0, 1710396975, '::1', '2024-03-13 16:06:43', '2024-03-21 15:15:04');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
@ -71,21 +71,6 @@ CREATE TABLE `chatgpt_api_keys` (
|
||||
`updated_at` datetime NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='OpenAI API ';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_api_keys`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_api_keys` (`id`, `platform`, `name`, `value`, `type`, `last_used_at`, `api_url`, `enabled`, `proxy_url`, `created_at`, `updated_at`) VALUES
|
||||
(11, 'ChatGLM', '智普', 'c3694f748a33d4d163bfa4b77ec1a153.p6s2NUZf5kAb0M6w', 'chat', 1710498745, 'https://open.bigmodel.cn/api/paas/v3/model-api/{model}/sse-invoke', 1, '', '2023-09-04 16:25:54', '2024-03-15 13:54:00'),
|
||||
(14, 'Baidu', '千帆', 'Wdpi8o3T8UWrxgVqjtq7HODS|SeseBNp9Y5rQdwlVG60isEofEMT8y9zz', 'chat', 1699519822, 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/{model}', 0, '', '2023-10-10 18:11:50', '2024-03-15 13:53:54'),
|
||||
(15, 'XunFei', '星火', 'b54dd11b|dbdcf59c74f8ea05e1792cad54409065|MTAxNjQ1MTVlODliNWM0NTVkOWM2ZTVm', 'chat', 1710497804, 'wss://spark-api.xf-yun.com/{version}/chat', 1, '', '2023-10-11 15:10:09', '2024-03-15 13:54:13'),
|
||||
(22, 'OpenAI', '官方', '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', 'img', 0, 'https://api.openai.com/v1/images/generations', 0, 'http://127.0.0.1:7777', '2023-12-07 11:48:36', '2024-03-18 15:32:58'),
|
||||
(24, 'OpenAI', '官方', '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', 'chat', 1710747118, 'https://api.openai.com/v1/chat/completions', 0, 'http://127.0.0.1:7777', '2024-01-04 10:10:58', '2024-03-18 15:32:59'),
|
||||
(25, 'OpenAI', '极客学长', 'sk-qR5kdnAhTPuiXf2r7d9bEbBdD6F64d9e81Dd9c75D716BdD4', 'chat', 1711586301, 'https://api.chat-plus.net/v1/chat/completions', 1, '', '2024-01-04 15:58:30', '2024-03-18 15:33:02'),
|
||||
(26, 'OpenAI', '极客学长', 'sk-n3JghlwzY6JaTIbv7064F94155F942D386F3F685F6Bd3b97', 'img', 1711007339, 'https://gpt.bemore.lol/v1/images/generations', 1, '', '2024-01-05 14:31:45', '2024-03-18 15:33:03'),
|
||||
(27, 'QWen', '通义千问', 'sk-6241c622828c44b1a2c70f495b16efac', 'chat', 1711334875, 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation', 1, '', '2024-01-19 10:39:22', '2024-03-15 11:55:02'),
|
||||
(28, 'OpenAI', '丈母娘', 'sk-tAKgJwKbi9cHzFMdC9C34f0207B74225977256F65972Fb94', 'img', 1708593118, 'https://www.jiujiuai.net/v1/images/generations', 0, '', '2024-02-22 17:06:02', '2024-02-23 09:29:26');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
@ -172,7 +157,8 @@ INSERT INTO `chatgpt_chat_models` (`id`, `platform`, `name`, `value`, `sort_num`
|
||||
(18, 'QWen', '通义千问-Plus', 'qwen-plus', 8, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:42:49', '2024-03-18 14:27:19'),
|
||||
(19, 'QWen', '通义千问-Max', 'qwen-max-1201', 9, 1, 1, 1.0, 1024, 32768, 1, '2024-01-19 10:51:03', '2024-03-18 14:27:19'),
|
||||
(21, 'OpenAI', '董宇辉小作文助手', 'gpt-4-gizmo-g-dse9iXvor', 6, 1, 30, 1.0, 8192, 32768, 0, '2024-03-18 14:24:20', '2024-03-18 14:27:19'),
|
||||
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 0, 1, 30, 1.0, 1024, 4096, 1, '2024-03-20 14:02:11', '2024-03-20 14:02:18');
|
||||
(22, 'OpenAI', 'LOGO生成神器', 'gpt-4-gizmo-g-YL87j8C7S', 0, 1, 30, 1.0, 1024, 4096, 1, '2024-03-20 14:02:11', '2024-03-20 14:02:18'),
|
||||
(23, 'OpenAI', '音乐生成器', 'suno-v3', 0, 1, 50, 0.8, 1024, 4096, 1, '2024-03-29 15:43:40', '2024-03-29 15:45:15');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
@ -236,7 +222,7 @@ CREATE TABLE `chatgpt_configs` (
|
||||
|
||||
INSERT INTO `chatgpt_configs` (`id`, `marker`, `config_json`) VALUES
|
||||
(1, 'system', '{\"title\":\"ChatPlus AI 智能助手\",\"admin_title\":\"ChatPlus 控制台\",\"logo\":\"http://localhost:5678/static/upload/2024/3/1711334798556619.png\",\"init_power\":100,\"daily_power\":99,\"invite_power\":10,\"vip_month_power\":1000,\"register_ways\":[\"mobile\",\"username\",\"email\"],\"enabled_register\":true,\"reward_img\":\"http://localhost:5678/static/upload/2024/3/1710753716309668.jpg\",\"enabled_reward\":true,\"power_price\":0.1,\"order_pay_timeout\":1800,\"vip_info_text\":\"月度会员,年度会员每月赠送 1000 点算力,赠送算力当月有效当月没有消费完的算力不结余到下个月。 点卡充值的算力长期有效。\",\"default_models\":[11,7,1,10,12,19,18,17,3],\"mj_power\":20,\"mj_action_power\":10,\"sd_power\":5,\"dall_power\":15,\"wechat_card_url\":\"/images/wx.png\",\"enable_context\":true,\"context_deep\":4}'),
|
||||
(3, 'notice', '{\"content\":\"注意:当前站点仅为开源项目 \\u003ca style=\\\"color: #F56C6C\\\" href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003eChatPlus\\u003c/a\\u003e 的演示项目,本项目单纯就是给大家体验项目功能使用。\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n体验额度用完之后请不要在当前站点进行任何充值操作!!!\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e 购买,现在有超级优惠,价格远低于 OpenAI 官方。\\nGPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\nMidJourney API 购买地址:\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
|
||||
(3, 'notice', '{\"content\":\"系统每日会给免费会员赠送10算力值,用完请第二天再来领取。\\n## v4.0.1 更新日志\\n* 功能重构:重构 Stable-Diffusion 绘画实现,使用 SDAPI 替换之前的 websocket 接口,SDAPI 兼容各种 stable-diffusion 发行版,稳定性更强一些\\n* 功能优化:使用 [midjouney-proxy](https://github.com/novicezk/midjourney-proxy) 项目替换内置的原生 MidJourney API,兼容 MJ-Plus 中转\\n* 功能新增:用户算力消费日志增加统计功能,统计一段时间内用户消费的算力\\n* Bug修复:修复 iphone 手机无法通过图形验证码的Bug,使用滑动验证码替换\\n* Bug修复:修复手机端 MidJourney 绘画页面滚动条无法滚动的Bug\\n\\n 如果觉得好用你就花几分钟自己部署一套,没有API KEY 的同学可以去\\u003ca href=\\\"https://api.chat-plus.net\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://api.chat-plus.net\\u003c/a\\u003e (支持MidJourney,GPT,Claude,Google Gemmi 各种表格模型) 或者 \\u003ca href=\\\"https://gpt.bemore.lol\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://gpt.bemore.lol\\u003c/a\\u003e(不支持 Midjourney) 购买,现在有超级优惠,价格远低于 OpenAI 官方。关于中转 API 的优势和劣势请参考 [中转API技术原理](https://ai.r9it.com/docs/install/errors-handle.html#%E8%B0%83%E7%94%A8%E4%B8%AD%E8%BD%AC-api-%E6%8A%A5%E9%94%99%E6%97%A0%E5%8F%AF%E7%94%A8%E6%B8%A0%E9%81%93)。\\nGPT-3.5,GPT-4,DALL-E3 绘图......你都可以随意使用,无需魔法。\\n接入教程: \\u003ca href=\\\"https://ai.r9it.com/docs/install/\\\" target=\\\"_blank\\\"\\n style=\\\"font-size: 20px;color:#F56C6C\\\"\\u003ehttps://ai.r9it.com/docs/install/\\u003c/a\\u003e\\n\\n本项目源码地址:\\u003ca href=\\\"https://github.com/yangjian102621/chatgpt-plus\\\" target=\\\"_blank\\\"\\u003ehttps://github.com/yangjian102621/chatgpt-plus\\u003c/a\\u003e\",\"updated\":true}');
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
@ -411,18 +397,6 @@ CREATE TABLE `chatgpt_products` (
|
||||
`url` varchar(255) DEFAULT NULL COMMENT '跳转地址'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员套餐表';
|
||||
|
||||
--
|
||||
-- 转存表中的数据 `chatgpt_products`
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_products` (`id`, `name`, `price`, `discount`, `days`, `power`, `enabled`, `sales`, `sort_num`, `created_at`, `updated_at`, `app_url`, `url`) VALUES
|
||||
(1, '会员1个月', '19.90', '10.00', 30, 0, 1, 2, 0, '2023-08-28 10:48:57', '2024-03-15 15:19:52', 'snssdk1128://ec_goods_detail?promotion_id=3668220199204683930', 'snssdk1128://ec_goods_detail?promotion_id=3668220199204683930'),
|
||||
(2, '会员3个月', '140.00', '30.00', 90, 0, 1, 1, 0, '2023-08-28 10:52:22', '2024-02-27 11:34:26', NULL, NULL),
|
||||
(3, '会员6个月', '290.00', '100.00', 180, 0, 1, 1, 0, '2023-08-28 10:53:39', '2023-08-31 16:24:36', NULL, NULL),
|
||||
(4, '会员12个月', '580.00', '200.00', 365, 0, 1, 1, 0, '2023-08-28 10:54:15', '2023-08-31 16:24:42', NULL, NULL),
|
||||
(5, '100算力', '10.00', '9.90', 0, 100, 1, 10, 0, '2023-08-28 10:55:08', '2024-03-15 15:20:29', NULL, 'snssdk1128://ec_goods_detail?promotion_id=3668220199204683930'),
|
||||
(6, '200算力', '29.90', '20.00', 0, 200, 1, 1, 0, '2023-12-15 16:55:12', '2024-03-15 15:20:37', NULL, NULL);
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
@ -496,7 +470,7 @@ CREATE TABLE `chatgpt_users` (
|
||||
--
|
||||
|
||||
INSERT INTO `chatgpt_users` (`id`, `username`, `nickname`, `password`, `avatar`, `salt`, `power`, `expired_time`, `status`, `chat_config_json`, `chat_roles_json`, `chat_models_json`, `last_login_at`, `vip`, `last_login_ip`, `created_at`, `updated_at`) VALUES
|
||||
(4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/2/1708682650912429.png', 'ueedue5l', 9434, 1717292086, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"programmer\",\"seller\"]', '[1,11]', 1711608964, 1, '::1', '2023-06-12 16:47:17', '2024-03-28 14:56:05'),
|
||||
(4, '18575670125', '极客学长@830270', 'ccc3fb7ab61b8b5d096a4a166ae21d121fc38c71bbd1be6173d9ab973214a63b', 'http://localhost:5678/static/upload/2024/2/1708682650912429.png', 'ueedue5l', 9384, 1717292086, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"red_book\",\"gpt\",\"programmer\",\"seller\"]', '[1,11]', 1711698298, 1, '::1', '2023-06-12 16:47:17', '2024-03-29 15:44:58'),
|
||||
(91, '18575670126', '极客学长@204872', '5e4050b8dd403f593260395d9edeb9f273dbe92d15dfdd929c4a182e95da10c4', '/images/avatar/user.png', '6fj0otl8', 33, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[1,11]', 1697184324, 1, '::1', '2023-10-13 16:01:56', '2024-03-25 11:07:45'),
|
||||
(100, '13777777777', '极客学长@292245', 'dcaf31b154432310bd700349e7de7e9dde2a3d6955a035a01fe527c7917a4f99', '/images/avatar/user.png', 'i8a53f8f', 99, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[1,11]', 0, 0, '', '2023-11-23 16:55:45', '2024-03-18 15:08:12'),
|
||||
(102, 'yangjian102621@gmail.com', '极客学长@207163', 'd51cec21942737083943e5c3a8f063dea034e40622ac8bd47d771f13707e4676', '/images/avatar/user.png', 'eqezapgk', 99, 0, 1, '{\"api_keys\":{\"Azure\":\"\",\"ChatGLM\":\"\",\"OpenAI\":\"\"}}', '[\"gpt\"]', '[1,11]', 1704448377, 0, '::1', '2024-01-05 17:48:00', '2024-03-18 15:08:41'),
|
||||
@ -667,7 +641,7 @@ ALTER TABLE `chatgpt_admin_users`
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_api_keys`
|
||||
--
|
||||
ALTER TABLE `chatgpt_api_keys`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=32;
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_history`
|
||||
@ -685,7 +659,7 @@ ALTER TABLE `chatgpt_chat_items`
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_models`
|
||||
--
|
||||
ALTER TABLE `chatgpt_chat_models`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23;
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=24;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_chat_roles`
|
||||
@ -745,7 +719,7 @@ ALTER TABLE `chatgpt_power_logs`
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_products`
|
||||
--
|
||||
ALTER TABLE `chatgpt_products`
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=7;
|
||||
MODIFY `id` int NOT NULL AUTO_INCREMENT;
|
||||
|
||||
--
|
||||
-- 使用表AUTO_INCREMENT `chatgpt_rewards`
|
BIN
web/public/images/menu/app.png
Normal file
After Width: | Height: | Size: 741 B |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.1 KiB |
BIN
web/public/images/menu/img-wall.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
web/public/images/menu/log.png
Normal file
After Width: | Height: | Size: 939 B |
BIN
web/public/images/menu/member.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
BIN
web/public/images/menu/share.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -173,7 +173,7 @@ const routes = [
|
||||
name: 'admin-manger',
|
||||
meta: {title: '管理员'},
|
||||
component: () => import('@/views/admin/Manager.vue'),
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
|
@ -313,12 +313,16 @@ httpGet("/api/config/get?key=system").then(res => {
|
||||
|
||||
// 获取系统公告
|
||||
httpGet("/api/config/get?key=notice").then(res => {
|
||||
notice.value = md.render(res.data['content'])
|
||||
const oldNotice = localStorage.getItem(noticeKey.value);
|
||||
// 如果公告有更新,则显示公告
|
||||
if (oldNotice !== notice.value && notice.value.length > 10) {
|
||||
showNotice.value = true
|
||||
try {
|
||||
notice.value = md.render(res.data['content'])
|
||||
const oldNotice = localStorage.getItem(noticeKey.value);
|
||||
// 如果公告有更新,则显示公告
|
||||
if (oldNotice !== notice.value && notice.value.length > 10) {
|
||||
showNotice.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
|
@ -7,14 +7,10 @@
|
||||
</div>
|
||||
<ul class="nav-items">
|
||||
<li v-for="item in navs" :key="item.path">
|
||||
<!-- <el-tooltip effect="light" :content="item.title" placement="right">-->
|
||||
<!-- -->
|
||||
<!-- </el-tooltip>-->
|
||||
<a @click="changeNav(item)" :class="item.path === curPath ? 'active' : ''">
|
||||
<el-image :src="item.icon_path" :width="20" v-if="item.icon_path"/>
|
||||
<i :class="'iconfont icon-' + item.icon" v-else></i>
|
||||
<el-image :src="item.icon" :width="20"/>
|
||||
</a>
|
||||
<div :class="item.path === curPath ? 'title active' : 'title'">{{ item.title }}</div>
|
||||
<div :class="item.url === curPath ? 'title active' : 'title'">{{ item.name }}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@ -37,21 +33,12 @@ import {ElMessage} from "element-plus";
|
||||
|
||||
const router = useRouter();
|
||||
const logo = ref('/images/logo.png');
|
||||
const navs = ref([
|
||||
{path: "/chat", icon_path: "/images/chat.png", title: "对话聊天"},
|
||||
{path: "/mj", icon_path: "/images/mj.png", title: "MJ 绘画"},
|
||||
{path: "/sd", icon_path: "/images/sd.png", title: "SD 绘画"},
|
||||
{path: "/apps", icon: "menu", title: "应用中心"},
|
||||
{path: "/images-wall", icon: "image-list", title: "作品展示"},
|
||||
{path: "/powerLog", icon: "log", title: "消费日志"},
|
||||
{path: "/member", icon: "vip-user", title: "会员计划"},
|
||||
{path: "/invite", icon: "share", title: "推广计划"},
|
||||
])
|
||||
const navs = ref([])
|
||||
const curPath = ref(router.currentRoute.value.path)
|
||||
|
||||
const changeNav = (item) => {
|
||||
curPath.value = item.path
|
||||
router.push(item.path)
|
||||
curPath.value = item.url
|
||||
router.push(item.url)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
@ -60,6 +47,12 @@ onMounted(() => {
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统配置失败:" + e.message)
|
||||
})
|
||||
// 获取菜单
|
||||
httpGet("/api/menu/list").then(res => {
|
||||
navs.value = res.data
|
||||
}).catch(e => {
|
||||
ElMessage.error("获取系统菜单失败:" + e.message)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
|
247
web/src/views/admin/Menu.vue
Normal file
@ -0,0 +1,247 @@
|
||||
<template>
|
||||
<div class="container menu" v-loading="loading">
|
||||
|
||||
<div class="handle-box">
|
||||
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
||||
</div>
|
||||
|
||||
<el-row>
|
||||
<el-table :data="items" :row-key="row => row.id" table-layout="auto">
|
||||
<el-table-column prop="name" label="菜单名称">
|
||||
<template #default="scope">
|
||||
<span class="sort" :data-id="scope.row.id">{{ scope.row.name }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="icon" label="菜单图标">
|
||||
<template #default="scope">
|
||||
<el-image class="menu-icon" :src="scope.row.icon"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="url" label="菜单URL"/>
|
||||
<el-table-column prop="enabled" label="启用状态">
|
||||
<template #default="scope">
|
||||
<el-switch v-model="scope.row['enabled']" @change="enable(scope.row)"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180">
|
||||
<template #default="scope">
|
||||
<el-button size="small" type="primary" @click="edit(scope.row)">编辑</el-button>
|
||||
<el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row)" :width="200">
|
||||
<template #reference>
|
||||
<el-button size="small" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-row>
|
||||
|
||||
<el-dialog
|
||||
v-model="showDialog"
|
||||
:title="title"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form :model="item" label-width="120px" ref="formRef" :rules="rules">
|
||||
<el-form-item label="菜单名称:" prop="name">
|
||||
<el-input v-model="item.name" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="菜单图标:" prop="icon">
|
||||
<el-input v-model="item.icon" placeholder="菜单图标地址">
|
||||
<template #append>
|
||||
<el-upload
|
||||
:auto-upload="true"
|
||||
:show-file-list="false"
|
||||
:http-request="uploadImg"
|
||||
>
|
||||
<el-icon class="uploader-icon">
|
||||
<UploadFilled/>
|
||||
</el-icon>
|
||||
</el-upload>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="菜单URL:" prop="url">
|
||||
<el-input v-model="item.url" autocomplete="off"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="启用状态:" prop="enable">
|
||||
<el-switch v-model="item.enabled"/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="showDialog = false">取消</el-button>
|
||||
<el-button type="primary" @click="save">提交</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {onMounted, reactive, ref} from "vue";
|
||||
import {httpGet, httpPost} from "@/utils/http";
|
||||
import {ElMessage} from "element-plus";
|
||||
import {dateFormat, removeArrayItem} from "@/utils/libs";
|
||||
import {Plus, UploadFilled} from "@element-plus/icons-vue";
|
||||
import {Sortable} from "sortablejs";
|
||||
import Compressor from "compressorjs";
|
||||
|
||||
// 变量定义
|
||||
const items = ref([])
|
||||
const item = ref({})
|
||||
const showDialog = ref(false)
|
||||
const title = ref("")
|
||||
const rules = reactive({
|
||||
name: [{required: true, message: '请输入菜单名称', trigger: 'change',}],
|
||||
icon: [{required: true, message: '请上传菜单图标', trigger: 'change',}],
|
||||
url: [{required: true, message: '请输入菜单地址', trigger: 'change',}],
|
||||
})
|
||||
const loading = ref(true)
|
||||
const formRef = ref(null)
|
||||
|
||||
const fetchData = () => {
|
||||
// 获取数据
|
||||
httpGet('/api/admin/menu/list').then((res) => {
|
||||
if (res.data) {
|
||||
// 初始化数据
|
||||
const arr = res.data;
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i].last_used_at = dateFormat(arr[i].last_used_at)
|
||||
}
|
||||
items.value = arr
|
||||
}
|
||||
loading.value = false
|
||||
}).catch(() => {
|
||||
ElMessage.error("获取数据失败");
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const drawBodyWrapper = document.querySelector('.el-table__body tbody')
|
||||
fetchData()
|
||||
|
||||
// 初始化拖动排序插件
|
||||
Sortable.create(drawBodyWrapper, {
|
||||
sort: true,
|
||||
animation: 500,
|
||||
onEnd({newIndex, oldIndex, from}) {
|
||||
if (oldIndex === newIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
const sortedData = Array.from(from.children).map(row => row.querySelector('.sort').getAttribute('data-id'));
|
||||
const ids = []
|
||||
const sorts = []
|
||||
sortedData.forEach((id, index) => {
|
||||
ids.push(parseInt(id))
|
||||
sorts.push(index)
|
||||
})
|
||||
|
||||
httpPost("/api/admin/menu/sort", {ids: ids, sorts: sorts}).catch(e => {
|
||||
ElMessage.error("排序失败:" + e.message)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const add = function () {
|
||||
title.value = "新增菜单"
|
||||
showDialog.value = true
|
||||
item.value = {}
|
||||
}
|
||||
|
||||
const edit = function (row) {
|
||||
title.value = "修改菜单"
|
||||
showDialog.value = true
|
||||
item.value = row
|
||||
}
|
||||
|
||||
const save = function () {
|
||||
formRef.value.validate((valid) => {
|
||||
if (valid) {
|
||||
showDialog.value = false
|
||||
if (!item.value.id) {
|
||||
item.value.sort_num = items.value.length + 1
|
||||
}
|
||||
httpPost('/api/admin/menu/save', item.value).then(() => {
|
||||
ElMessage.success('操作成功!')
|
||||
fetchData()
|
||||
}).catch((e) => {
|
||||
ElMessage.error('操作失败,' + e.message)
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const enable = (row) => {
|
||||
httpPost('/api/admin/menu/enable', {id: row.id, enabled: row.enabled}).then(() => {
|
||||
ElMessage.success("操作成功!")
|
||||
}).catch(e => {
|
||||
ElMessage.error("操作失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
const remove = function (row) {
|
||||
httpGet('/api/admin/menu/remove?id=' + row.id).then(() => {
|
||||
ElMessage.success("删除成功!")
|
||||
items.value = removeArrayItem(items.value, row, (v1, v2) => {
|
||||
return v1.id === v2.id
|
||||
})
|
||||
}).catch((e) => {
|
||||
ElMessage.error("删除失败:" + e.message)
|
||||
})
|
||||
}
|
||||
|
||||
// 图片上传
|
||||
const uploadImg = (file) => {
|
||||
// 压缩图片并上传
|
||||
new Compressor(file.file, {
|
||||
quality: 0.6,
|
||||
success(result) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', result, result.name);
|
||||
// 执行上传操作
|
||||
httpPost('/api/admin/upload', formData).then((res) => {
|
||||
item.value.icon = res.data.url
|
||||
ElMessage.success('上传成功')
|
||||
}).catch((e) => {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
})
|
||||
},
|
||||
error(e) {
|
||||
ElMessage.error('上传失败:' + e.message)
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.menu {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
display flex;
|
||||
justify-content flex-end
|
||||
|
||||
.el-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width 36px
|
||||
height 36px
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="container list" v-loading="loading">
|
||||
<div class="container product" v-loading="loading">
|
||||
|
||||
<div class="handle-box">
|
||||
<el-button type="primary" :icon="Plus" @click="add">新增</el-button>
|
||||
@ -153,13 +153,13 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
const add = function () {
|
||||
title.value = "新增模型"
|
||||
title.value = "新增产品"
|
||||
showDialog.value = true
|
||||
item.value = {}
|
||||
}
|
||||
|
||||
const edit = function (row) {
|
||||
title.value = "修改模型"
|
||||
title.value = "修改产品"
|
||||
showDialog.value = true
|
||||
item.value = row
|
||||
}
|
||||
@ -206,7 +206,7 @@ const remove = function (row) {
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.list {
|
||||
.product {
|
||||
|
||||
.opt-box {
|
||||
padding-bottom: 10px;
|
||||
|
@ -253,6 +253,10 @@
|
||||
<el-button type="primary" @click="save('notice')">保存</el-button>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="菜单配置" name="menu">
|
||||
<Menu/>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
@ -265,6 +269,7 @@ import {ElMessage} from "element-plus";
|
||||
import {InfoFilled, UploadFilled} from "@element-plus/icons-vue";
|
||||
import MdEditor from "md-editor-v3";
|
||||
import 'md-editor-v3/lib/style.css';
|
||||
import Menu from "@/views/admin/Menu.vue";
|
||||
|
||||
const activeName = ref('basic')
|
||||
const system = ref({models: []})
|
||||
|