enable to update AI Drawing configuarations in admin console page

This commit is contained in:
RockYang 2024-05-11 17:27:14 +08:00
parent 1a4d798f8b
commit e35c34ad9a
16 changed files with 343 additions and 108 deletions

View File

@ -6,6 +6,8 @@
* 功能新增:给思维导图增加 ToolBar实现思维导图的放大缩小和定位
* Bug修复修复思维导图不扣费的Bug
* Bug修复修复管理后台角色删除失败的Bug
* Bug修复兼容最新版秋叶SD懒人包的 SD API新增 scheduler 参数
* 功能优化:支持在管理后台配置 AI 绘图相关配置,包括 SD, MJ-PLUS, MJ-PROXY
## v4.0.5
@ -45,6 +47,7 @@
* 功能新增:支持管理后台 Logo 修改
## 4.0.2
* 功能新增:支持前端菜单可以配置
* 功能优化:在登录和注册界面标题显示软件版本号
* 功能优化MJ 绘画支持 --sref 和 --cref 图片一致性参数
@ -54,6 +57,7 @@
* 功能新增:管理后台登录页面增加行为验证码,防止爆破
## v4.0.1
* 功能重构:重构 Stable-Diffusion 绘画实现,使用 SDAPI 替换之前的 websocket 接口SDAPI 兼容各种 stable-diffusion
发行版,稳定性更强一些
* 功能优化:使用 [midjouney-proxy](https://github.com/novicezk/midjourney-proxy) 项目替换内置的原生 MidJourney API兼容
@ -63,6 +67,7 @@
* Bug修复修复手机端 MidJourney 绘画页面滚动条无法滚动的Bug
## v4.0.0
非兼容版本重大重构引入算力概念将系统中所有的能力AI对话MJ绘画SD绘画DALL绘画全部使用算力来兑换。
只要你的算力值余额不为0你就可以进行任何操作。比如一次 GPT3.5 对话消耗1个单位算力一次 GPT4 对话消耗10个算力。一次 MJ
对话消耗15个算力...

View File

@ -55,9 +55,10 @@ type SdTaskParams struct {
NegPrompt string `json:"neg_prompt"` // 反向提示词
Steps int `json:"steps"` // 迭代步数默认20
Sampler string `json:"sampler"` // 采样器
FaceFix bool `json:"face_fix"` // 面部修复
CfgScale float32 `json:"cfg_scale"` //引导系数,默认 7
Seed int64 `json:"seed"` // 随机数种子
Scheduler string `json:"scheduler"`
FaceFix bool `json:"face_fix"` // 面部修复
CfgScale float32 `json:"cfg_scale"` //引导系数,默认 7
Seed int64 `json:"seed"` // 随机数种子
Height int `json:"height"`
Width int `json:"width"`
HdFix bool `json:"hd_fix"` // 启用高清修复

View File

@ -12,6 +12,8 @@ import (
"geekai/core/types"
"geekai/handler"
"geekai/service"
"geekai/service/mj"
"geekai/service/sd"
"geekai/store"
"geekai/store/model"
"geekai/utils"
@ -26,10 +28,18 @@ type ConfigHandler struct {
handler.BaseHandler
levelDB *store.LevelDB
licenseService *service.LicenseService
mjServicePool *mj.ServicePool
sdServicePool *sd.ServicePool
}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, licenseService *service.LicenseService) *ConfigHandler {
return &ConfigHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}, levelDB: levelDB, licenseService: licenseService}
func NewConfigHandler(app *core.AppServer, db *gorm.DB, levelDB *store.LevelDB, licenseService *service.LicenseService, mjPool *mj.ServicePool, sdPool *sd.ServicePool) *ConfigHandler {
return &ConfigHandler{
BaseHandler: handler.BaseHandler{App: app, DB: db},
levelDB: levelDB,
mjServicePool: mjPool,
sdServicePool: sdPool,
licenseService: licenseService,
}
}
func (h *ConfigHandler) Update(c *gin.Context) {
@ -138,3 +148,49 @@ func (h *ConfigHandler) GetDrawingConfig(c *gin.Context) {
"sd": h.App.Config.SdConfigs,
})
}
// SaveDrawingConfig 保存AI绘画配置
func (h *ConfigHandler) SaveDrawingConfig(c *gin.Context) {
var data struct {
Sd []types.StableDiffusionConfig `json:"sd"`
MjPlus []types.MjPlusConfig `json:"mj_plus"`
MjProxy []types.MjProxyConfig `json:"mj_proxy"`
}
if err := c.ShouldBindJSON(&data); err != nil {
resp.ERROR(c, types.InvalidArgs)
return
}
changed := false
if configChanged(data.Sd, h.App.Config.SdConfigs) {
logger.Debugf("SD 配置变动了")
h.App.Config.SdConfigs = data.Sd
h.sdServicePool.InitServices(data.Sd)
changed = true
}
if configChanged(data.MjPlus, h.App.Config.MjPlusConfigs) || configChanged(data.MjProxy, h.App.Config.MjProxyConfigs) {
logger.Debugf("MidJourney 配置变动了")
h.App.Config.MjPlusConfigs = data.MjPlus
h.App.Config.MjProxyConfigs = data.MjProxy
h.mjServicePool.InitServices(data.MjPlus, data.MjProxy)
changed = true
}
if changed {
err := core.SaveConfig(h.App.Config)
if err != nil {
resp.ERROR(c, "更新配置文档失败!")
return
}
}
resp.SUCCESS(c)
}
func configChanged(c1 interface{}, c2 interface{}) bool {
encode1 := utils.JsonEncode(c1)
encode2 := utils.JsonEncode(c2)
return utils.Md5(encode1) != utils.Md5(encode2)
}

View File

@ -8,6 +8,7 @@ package admin
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/handler"
@ -16,7 +17,6 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"fmt"
"time"
"github.com/gin-gonic/gin"
@ -87,7 +87,7 @@ func (h *UserHandler) Save(c *gin.Context) {
// 检测最大注册人数
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if int(totalUser) >= h.licenseService.GetLicense().UserNum {
if h.licenseService.GetLicense().UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}

View File

@ -8,6 +8,7 @@ package handler
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import (
"fmt"
"geekai/core"
"geekai/core/types"
"geekai/service"
@ -15,7 +16,6 @@ import (
"geekai/store/vo"
"geekai/utils"
"geekai/utils/resp"
"fmt"
"strings"
"time"
@ -71,7 +71,7 @@ func (h *UserHandler) Register(c *gin.Context) {
// 检测最大注册人数
var totalUser int64
h.DB.Model(&model.User{}).Count(&totalUser)
if int(totalUser) >= h.licenseService.GetLicense().UserNum {
if h.licenseService.GetLicense().UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().UserNum {
resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
return
}

View File

@ -190,7 +190,8 @@ func main() {
// MidJourney service pool
fx.Provide(mj.NewServicePool),
fx.Invoke(func(pool *mj.ServicePool) {
fx.Invoke(func(pool *mj.ServicePool, config *types.AppConfig) {
pool.InitServices(config.MjPlusConfigs, config.MjProxyConfigs)
if pool.HasAvailableService() {
pool.DownloadImages()
pool.CheckTaskNotify()
@ -200,7 +201,8 @@ func main() {
// Stable Diffusion 机器人
fx.Provide(sd.NewServicePool),
fx.Invoke(func(pool *sd.ServicePool) {
fx.Invoke(func(pool *sd.ServicePool, config *types.AppConfig) {
pool.InitServices(config.SdConfigs)
if pool.HasAvailableService() {
pool.CheckTaskNotify()
pool.CheckTaskStatus()
@ -303,6 +305,7 @@ func main() {
group.POST("active", h.Active)
group.GET("config/get/license", h.GetLicense)
group.GET("config/get/draw", h.GetDrawingConfig)
group.POST("config/update/draw", h.SaveDrawingConfig)
}),
fx.Invoke(func(s *core.AppServer, h *admin.ManagerHandler) {
group := s.Engine.Group("/api/admin/")

View File

@ -31,48 +31,15 @@ type ServicePool struct {
db *gorm.DB
uploaderManager *oss.UploaderManager
Clients *types.LMap[uint, *types.WsClient] // UserId => Client
licenseService *service.LicenseService
}
var logger = logger2.GetLogger()
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig, licenseService *service.LicenseService) *ServicePool {
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, licenseService *service.LicenseService) *ServicePool {
services := make([]*Service, 0)
taskQueue := store.NewRedisQueue("MidJourney_Task_Queue", redisCli)
notifyQueue := store.NewRedisQueue("MidJourney_Notify_Queue", redisCli)
for k, config := range appConfig.MjPlusConfigs {
if config.Enabled == false {
continue
}
err := licenseService.IsValidApiURL(config.ApiURL)
if err != nil {
logger.Error(err)
continue
}
cli := NewPlusClient(config)
name := fmt.Sprintf("mj-plus-service-%d", k)
plusService := NewService(name, taskQueue, notifyQueue, db, cli)
go func() {
plusService.Run()
}()
services = append(services, plusService)
}
// for mid-journey proxy
for k, config := range appConfig.MjProxyConfigs {
if config.Enabled == false {
continue
}
cli := NewProxyClient(config)
name := fmt.Sprintf("mj-proxy-service-%d", k)
proxyService := NewService(name, taskQueue, notifyQueue, db, cli)
go func() {
proxyService.Run()
}()
services = append(services, proxyService)
}
return &ServicePool{
taskQueue: taskQueue,
notifyQueue: notifyQueue,
@ -80,6 +47,47 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
uploaderManager: manager,
db: db,
Clients: types.NewLMap[uint, *types.WsClient](),
licenseService: licenseService,
}
}
func (p *ServicePool) InitServices(plusConfigs []types.MjPlusConfig, proxyConfigs []types.MjProxyConfig) {
// stop old service
for _, s := range p.services {
s.Stop()
}
for k, config := range plusConfigs {
if config.Enabled == false {
continue
}
err := p.licenseService.IsValidApiURL(config.ApiURL)
if err != nil {
logger.Errorf("创建 MJ-PLUS 服务失败:%v", err)
continue
}
cli := NewPlusClient(config)
name := fmt.Sprintf("mj-plus-service-%d", k)
plusService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
go func() {
plusService.Run()
}()
p.services = append(p.services, plusService)
}
// for mid-journey proxy
for k, config := range proxyConfigs {
if config.Enabled == false {
continue
}
cli := NewProxyClient(config)
name := fmt.Sprintf("mj-proxy-service-%d", k)
proxyService := NewService(name, p.taskQueue, p.notifyQueue, p.db, cli)
go func() {
proxyService.Run()
}()
p.services = append(p.services, proxyService)
}
}

View File

@ -28,6 +28,7 @@ type Service struct {
taskQueue *store.RedisQueue
notifyQueue *store.RedisQueue
db *gorm.DB
running bool
}
func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, db *gorm.DB, cli Client) *Service {
@ -37,12 +38,13 @@ func NewService(name string, taskQueue *store.RedisQueue, notifyQueue *store.Red
taskQueue: taskQueue,
notifyQueue: notifyQueue,
Client: cli,
running: true,
}
}
func (s *Service) Run() {
logger.Infof("Starting MidJourney job consumer for %s", s.Name)
for {
for s.running {
var task types.MjTask
err := s.taskQueue.LPop(&task)
if err != nil {
@ -125,6 +127,11 @@ func (s *Service) Run() {
}
}
func (s *Service) Stop() {
s.running = false
s.Client = nil
}
type CBReq struct {
Id string `json:"id"`
Action string `json:"action"`

View File

@ -25,28 +25,14 @@ type ServicePool struct {
notifyQueue *store.RedisQueue
db *gorm.DB
Clients *types.LMap[uint, *types.WsClient] // UserId => Client
uploader *oss.UploaderManager
levelDB *store.LevelDB
}
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, appConfig *types.AppConfig, levelDB *store.LevelDB) *ServicePool {
func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderManager, levelDB *store.LevelDB) *ServicePool {
services := make([]*Service, 0)
taskQueue := store.NewRedisQueue("StableDiffusion_Task_Queue", redisCli)
notifyQueue := store.NewRedisQueue("StableDiffusion_Queue", redisCli)
// create mj client and service
for _, config := range appConfig.SdConfigs {
if config.Enabled == false {
continue
}
// create sd service
name := fmt.Sprintf("StableDifffusion Service-%s", config.Model)
service := NewService(name, config, taskQueue, notifyQueue, db, manager, levelDB)
// run sd service
go func() {
service.Run()
}()
services = append(services, service)
}
return &ServicePool{
taskQueue: taskQueue,
@ -54,6 +40,31 @@ func NewServicePool(db *gorm.DB, redisCli *redis.Client, manager *oss.UploaderMa
services: services,
db: db,
Clients: types.NewLMap[uint, *types.WsClient](),
uploader: manager,
levelDB: levelDB,
}
}
func (p *ServicePool) InitServices(configs []types.StableDiffusionConfig) {
// stop old service
for _, s := range p.services {
s.Stop()
}
for k, config := range configs {
if config.Enabled == false {
continue
}
// create sd service
name := fmt.Sprintf(" sd-service-%d", k)
service := NewService(name, config, p.taskQueue, p.notifyQueue, p.db, p.uploader, p.levelDB)
// run sd service
go func() {
service.Run()
}()
p.services = append(p.services, service)
}
}

View File

@ -33,6 +33,7 @@ type Service struct {
uploadManager *oss.UploaderManager
name string // service name
leveldb *store.LevelDB
running bool // 运行状态
}
func NewService(name string, config types.StableDiffusionConfig, taskQueue *store.RedisQueue, notifyQueue *store.RedisQueue, db *gorm.DB, manager *oss.UploaderManager, levelDB *store.LevelDB) *Service {
@ -46,18 +47,20 @@ func NewService(name string, config types.StableDiffusionConfig, taskQueue *stor
db: db,
leveldb: levelDB,
uploadManager: manager,
running: true,
}
}
func (s *Service) Run() {
for {
logger.Infof("Starting Stable-Diffusion job consumer for %s", s.name)
for s.running {
var task types.SdTask
err := s.taskQueue.LPop(&task)
if err != nil {
logger.Errorf("taking task with error: %v", err)
continue
}
logger.Infof("%s handle a new Stable-Diffusion task: %+v", s.name, task)
// translate prompt
if utils.HasChinese(task.Params.Prompt) {
content, err := utils.OpenAIRequest(s.db, fmt.Sprintf(service.RewritePromptTemplate, task.Params.Prompt))
@ -94,6 +97,10 @@ func (s *Service) Run() {
}
}
func (s *Service) Stop() {
s.running = false
}
// Txt2ImgReq 文生图请求实体
type Txt2ImgReq struct {
Prompt string `json:"prompt"`
@ -104,6 +111,7 @@ type Txt2ImgReq struct {
Width int `json:"width"`
Height int `json:"height"`
SamplerName string `json:"sampler_name"`
Scheduler string `json:"scheduler"`
EnableHr bool `json:"enable_hr,omitempty"`
HrScale int `json:"hr_scale,omitempty"`
HrUpscaler string `json:"hr_upscaler,omitempty"`
@ -137,6 +145,7 @@ func (s *Service) Txt2Img(task types.SdTask) error {
Width: task.Params.Width,
Height: task.Params.Height,
SamplerName: task.Params.Sampler,
Scheduler: task.Params.Scheduler,
ForceTaskId: task.Params.TaskId,
}
if task.Params.Seed > 0 {

View File

@ -6,7 +6,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<link rel="icon" href="/favicon.ico" type="image/x-icon">
<title>ChatGPT-Plus</title>
<title>Geek-AI 创作助手</title>
</head>
<body>

View File

@ -105,7 +105,7 @@ const routes = [
{
path: '/admin/login',
name: 'admin-login',
meta: {title: 'ChatPuls 控制台登录'},
meta: {title: 'Geek-AI 控制台登录'},
component: () => import('@/views/admin/Login.vue'),
},
{
@ -113,7 +113,7 @@ const routes = [
path: '/admin',
redirect: '/admin/dashboard',
component: () => import("@/views/admin/Home.vue"),
meta: {title: 'ChatPuls 管理后台'},
meta: {title: 'Geek-AI 控制台'},
children: [
{
path: '/admin/dashboard',

View File

@ -29,6 +29,28 @@
</el-form-item>
</div>
<div class="param-line" style="padding-top: 10px">
<el-form-item label="采样调度器">
<template #default>
<div class="form-item-inner">
<el-select v-model="params.scheduler" style="width:176px">
<el-option v-for="item in schedulers" :label="item" :value="item" :key="item"/>
</el-select>
<el-tooltip
effect="light"
content="推荐自动或者 Karras"
raw-content
placement="right"
>
<el-icon class="info-icon">
<InfoFilled/>
</el-icon>
</el-tooltip>
</div>
</template>
</el-form-item>
</div>
<div class="param-line">
<el-form-item label="图片尺寸">
<template #default>
@ -509,12 +531,14 @@ window.onresize = () => {
listBoxHeight.value = window.innerHeight - 40
paramBoxHeight.value = window.innerHeight - 150
}
const samplers = ["Euler a", "DPM++ 2S a Karras", "DPM++ 2M Karras", "DPM++ SDE Karras", "DPM++ 2M SDE Karras"]
const samplers = ["Euler a", "DPM++ 2S a", "DPM++ 2M", "DPM++ SDE", "DPM++ 2M SDE", "UniPC", "Restart"]
const schedulers = ["Automatic", "Karras", "Exponential", "Uniform"]
const scaleAlg = ["Latent", "ESRGAN_4x", "R-ESRGAN 4x+", "SwinIR_4x", "LDSR"]
const params = ref({
width: 1024,
height: 1024,
sampler: samplers[0],
scheduler: schedulers[0],
seed: -1,
steps: 20,
cfg_scale: 7,

View File

@ -1,15 +1,34 @@
<template>
<el-form label-width="150px" label-position="right">
<el-form label-width="150px" label-position="right" class="draw-config">
<el-tabs type="border-card">
<el-tab-pane label="MJ-PLUS">
<div v-if="mjPlusConfigs">
<el-form-item label="网站标题">
<el-input v-model="sdConfigs"/>
</el-form-item>
<div class="config-item" v-for="(v,k) in mjPlusConfigs">
<el-form-item label="是否启用">
<el-switch v-model="v['Enabled']"/>
</el-form-item>
<el-form-item label="API 地址">
<el-input v-model="v['ApiURL']" placeholder="API 地址"/>
</el-form-item>
<el-form-item label="API 令牌">
<el-input v-model="v['ApiKey']" placeholder="API KEY"/>
</el-form-item>
<el-form-item label="绘画模式">
<el-select v-model="v['Mode']" placeholder="请选择模式">
<el-option v-for="item in mjModels" :value="item.value" :label="item.name" :key="item.value">{{
item.name
}}
</el-option>
</el-select>
</el-form-item>
<el-button class="remove" type="danger" :icon="Delete" circle @click="removeItem(mjPlusConfigs,k)"/>
</div>
</div>
<el-empty v-else></el-empty>
<el-row style="justify-content: center">
<el-button round>
<el-row style="justify-content: center; padding: 10px">
<el-button round @click="addConfig(mjPlusConfigs)">
<el-icon><Plus /></el-icon>
<span>新增配置</span>
</el-button>
@ -19,23 +38,66 @@
<el-tab-pane label="MJ-PROXY">
<div v-if="mjProxyConfigs">
<el-form-item label="注册赠送算力">
<el-input v-model.number="sdConfigs" placeholder="新用户注册赠送算力"/>
</el-form-item>
<div class="config-item" v-for="(v,k) in mjProxyConfigs">
<el-form-item label="是否启用">
<el-switch v-model="v['Enabled']"/>
</el-form-item>
<el-form-item label="API 地址">
<el-input v-model="v['ApiURL']" placeholder="API 地址"/>
</el-form-item>
<el-form-item label="API 令牌">
<el-input v-model="v['ApiKey']" placeholder="API KEY"/>
</el-form-item>
<el-button class="remove" type="danger" :icon="Delete" circle @click="removeItem(mjProxyConfigs,k)"/>
</div>
</div>
<el-empty v-else />
<el-row style="justify-content: center; padding: 10px">
<el-button round @click="addConfig(mjProxyConfigs)">
<el-icon>
<Plus/>
</el-icon>
<span>新增配置</span>
</el-button>
</el-row>
</el-tab-pane>
<el-tab-pane label="Stable-Diffusion">
<el-form-item label="注册赠送算力">
<el-input v-model.number="sdConfigs" placeholder="新用户注册赠送算力"/>
</el-form-item>
<div v-if="sdConfigs">
<div class="config-item" v-for="(v,k) in sdConfigs">
<el-form-item label="是否启用">
<el-switch v-model="v['Enabled']"/>
</el-form-item>
<el-form-item label="API 地址">
<el-input v-model="v['ApiURL']" placeholder="API 地址"/>
</el-form-item>
<el-form-item label="API 令牌">
<el-input v-model="v['ApiKey']" placeholder="API KEY"/>
</el-form-item>
<el-form-item label="模型">
<el-input v-model="v['Model']" placeholder="绘画模型"/>
</el-form-item>
<el-button class="remove" type="danger" :icon="Delete" circle @click="removeItem(sdConfigs,k)"/>
</div>
</div>
<el-empty v-else/>
<el-row style="justify-content: center; padding: 10px">
<el-button round @click="addConfig(sdConfigs)">
<el-icon>
<Plus/>
</el-icon>
<span>新增配置</span>
</el-button>
</el-row>
</el-tab-pane>
</el-tabs>
<div style="padding: 10px;">
<el-form-item>
<el-button type="primary" @click="save('system')">保存</el-button>
<el-button type="primary" @click="saveConfig()">保存</el-button>
</el-form-item>
</div>
</el-form>
@ -43,14 +105,19 @@
<script setup>
import {ref} from "vue";
import {httpGet} from "@/utils/http";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {Plus} from "@element-plus/icons-vue";
import {Delete, Plus} from "@element-plus/icons-vue";
//
const sdConfigs = ref([])
const mjPlusConfigs = ref([])
const mjProxyConfigs = ref([])
const mjModels = ref([
{name: "慢速Relax", value: "relax"},
{name: "快速Fast", value: "fast"},
{name: "急速Turbo", value: "turbo"},
])
httpGet("/api/admin/config/get/draw").then(res => {
sdConfigs.value = res.data.sd
@ -59,29 +126,48 @@ httpGet("/api/admin/config/get/draw").then(res => {
}).catch(e =>{
ElMessage.error("获取AI绘画配置失败"+e.message)
})
const addConfig = (configs) => {
configs.push({
Enabled: true,
ApiKey: '',
ApiURL: '',
Mode: 'fast'
})
}
const saveConfig = () => {
httpPost('/api/admin/config/update/draw', {
'sd': sdConfigs.value,
'mj_plus': mjPlusConfigs.value,
'mj_proxy': mjProxyConfigs.value
}).then(() => {
ElMessage.success("配置更新成功")
}).catch(e => {
ElMessage.error("操作失败:" + e.message)
})
}
const removeItem = (arr, k) => {
arr.splice(k, 1)
}
</script>
<style lang="stylus" scoped>
.menu {
.draw-config {
.opt-box {
padding-bottom: 10px;
display flex;
justify-content flex-end
.config-item {
position relative
padding 15px 10px 10px 10px
border 1px solid var(--el-border-color)
border-radius 10px
margin-bottom 10px
.el-icon {
margin-right: 5px;
.remove {
position absolute
right 15px
top 15px
}
}
.menu-icon {
width 36px
height 36px
}
.el-select {
width: 100%
}
}
</style>

View File

@ -2,6 +2,10 @@
<div class="admin-login">
<div class="main">
<div class="contain">
<div class="logo">
<el-image :src="logo" fit="cover" @click="router.push('/')"/>
</div>
<div class="header">{{ title }}</div>
<div class="content">
<div class="block">
@ -42,9 +46,9 @@
<script setup>
import {onMounted, ref} from "vue";
import {ref} from "vue";
import {Lock, UserFilled} from "@element-plus/icons-vue";
import {httpPost} from "@/utils/http";
import {httpGet, httpPost} from "@/utils/http";
import {ElMessage} from "element-plus";
import {useRouter} from "vue-router";
import FooterBar from "@/components/FooterBar.vue";
@ -52,15 +56,24 @@ import {setAdminToken} from "@/store/session";
import {checkAdminSession} from "@/action/session";
const router = useRouter();
const title = ref('ChatGPT Plus Admin');
const title = ref('Geek-AI Console');
const username = ref(process.env.VUE_APP_ADMIN_USER);
const password = ref(process.env.VUE_APP_ADMIN_PASS);
const logo = ref("/images/logo.png")
checkAdminSession().then(() => {
router.push("/admin")
}).catch(() => {
})
//
httpGet('/api/config/get?key=system').then(res => {
title.value = res.data.admin_title
logo.value = res.data.logo
}).catch(e => {
ElMessage.error("加载系统配置失败: " + e.message)
})
const keyupHandle = (e) => {
if (e.key === 'Enter') {
login();
@ -107,10 +120,20 @@ const login = function () {
border-radius 10px;
background rgba(255, 255, 255, 0.3)
.logo {
text-align center
.el-image {
width 120px;
cursor pointer
}
}
.header {
width 100%
margin-bottom 20px
font-size 24px
padding 10px
font-size 26px
text-align center
}

View File

@ -279,7 +279,9 @@
<el-tab-pane label="公告配置" name="notice">
<md-editor class="mgb20" v-model="notice" @on-upload-img="onUploadImg"/>
<el-form-item>
<el-button type="primary" @click="save('notice')">保存</el-button>
<div style="padding-top: 10px;margin-left: 150px;">
<el-button type="primary" @click="save('notice')">保存</el-button>
</div>
</el-form-item>
</el-tab-pane>