mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 16:23:42 +08:00 
			
		
		
		
	image task list page for admin console is ready
This commit is contained in:
		@@ -1,4 +1,8 @@
 | 
			
		||||
# 更新日志
 | 
			
		||||
## v4.1.6
 | 
			
		||||
* 功能优化:优化MysQL容器配置文档,解决MysQL容器资源占用过高问题
 | 
			
		||||
* 功能新增:管理后台增加AI绘图任务管理,可在管理后台浏览和删除用户的绘图任务
 | 
			
		||||
 | 
			
		||||
## v4.1.5
 | 
			
		||||
* 功能优化:重构 websocket 组件,减少 websocket 连接数,全站共享一个 websocket 连接
 | 
			
		||||
* Bug修复:兼容手机端原生微信支付和支付宝支付渠道
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										174
									
								
								api/handler/admin/image_handler.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								api/handler/admin/image_handler.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
			
		||||
package admin
 | 
			
		||||
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
// * Copyright 2023 The Geek-AI Authors. All rights reserved.
 | 
			
		||||
// * Use of this source code is governed by a Apache-2.0 license
 | 
			
		||||
// * that can be found in the LICENSE file.
 | 
			
		||||
// * @Author yangjian102621@163.com
 | 
			
		||||
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"geekai/core"
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	"geekai/handler"
 | 
			
		||||
	"geekai/store/model"
 | 
			
		||||
	"geekai/store/vo"
 | 
			
		||||
	"geekai/utils"
 | 
			
		||||
	"geekai/utils/resp"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"gorm.io/gorm"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ImageHandler struct {
 | 
			
		||||
	handler.BaseHandler
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewImageHandler(app *core.AppServer, db *gorm.DB) *ImageHandler {
 | 
			
		||||
	return &ImageHandler{BaseHandler: handler.BaseHandler{App: app, DB: db}}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type query struct {
 | 
			
		||||
	Prompt    string   `json:"prompt"`
 | 
			
		||||
	Username  string   `json:"username"`
 | 
			
		||||
	CreatedAt []string `json:"created_time"`
 | 
			
		||||
	Page      int      `json:"page"`
 | 
			
		||||
	PageSize  int      `json:"page_size"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MjList Midjourney 任务列表
 | 
			
		||||
func (h *ImageHandler) MjList(c *gin.Context) {
 | 
			
		||||
	var data query
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{})
 | 
			
		||||
	if data.Username != "" {
 | 
			
		||||
		var user model.User
 | 
			
		||||
		err := h.DB.Where("username", data.Username).First(&user).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			session = session.Where("user_id", user.Id)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if data.Prompt != "" {
 | 
			
		||||
		session = session.Where("prompt LIKE ?", "%"+data.Prompt+"%")
 | 
			
		||||
	}
 | 
			
		||||
	if len(data.CreatedAt) == 2 {
 | 
			
		||||
		start := utils.Str2stamp(data.CreatedAt[0] + " 00:00:00")
 | 
			
		||||
		end := utils.Str2stamp(data.CreatedAt[1] + " 00:00:00")
 | 
			
		||||
		session = session.Where("created_at >= ? AND created_at <= ?", start, end)
 | 
			
		||||
	}
 | 
			
		||||
	var total int64
 | 
			
		||||
	session.Model(&model.MidJourneyJob{}).Count(&total)
 | 
			
		||||
	var list []model.MidJourneyJob
 | 
			
		||||
	var items = make([]vo.MidJourneyJob, 0)
 | 
			
		||||
	offset := (data.Page - 1) * data.PageSize
 | 
			
		||||
	err := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&list).Error
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// 填充数据
 | 
			
		||||
		for _, item := range list {
 | 
			
		||||
			var job vo.MidJourneyJob
 | 
			
		||||
			err = utils.CopyObject(item, &job)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			job.CreatedAt = item.CreatedAt.Unix()
 | 
			
		||||
			items = append(items, job)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, items))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SdList Stable Diffusion 任务列表
 | 
			
		||||
func (h *ImageHandler) SdList(c *gin.Context) {
 | 
			
		||||
	var data query
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{})
 | 
			
		||||
	if data.Username != "" {
 | 
			
		||||
		var user model.User
 | 
			
		||||
		err := h.DB.Where("username", data.Username).First(&user).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			session = session.Where("user_id", user.Id)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if data.Prompt != "" {
 | 
			
		||||
		session = session.Where("prompt LIKE ?", "%"+data.Prompt+"%")
 | 
			
		||||
	}
 | 
			
		||||
	if len(data.CreatedAt) == 2 {
 | 
			
		||||
		start := utils.Str2stamp(data.CreatedAt[0] + " 00:00:00")
 | 
			
		||||
		end := utils.Str2stamp(data.CreatedAt[1] + " 00:00:00")
 | 
			
		||||
		session = session.Where("created_at >= ? AND created_at <= ?", start, end)
 | 
			
		||||
	}
 | 
			
		||||
	var total int64
 | 
			
		||||
	session.Model(&model.SdJob{}).Count(&total)
 | 
			
		||||
	var list []model.SdJob
 | 
			
		||||
	var items = make([]vo.SdJob, 0)
 | 
			
		||||
	offset := (data.Page - 1) * data.PageSize
 | 
			
		||||
	err := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&list).Error
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// 填充数据
 | 
			
		||||
		for _, item := range list {
 | 
			
		||||
			var job vo.SdJob
 | 
			
		||||
			err = utils.CopyObject(item, &job)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			job.CreatedAt = item.CreatedAt.Unix()
 | 
			
		||||
			items = append(items, job)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, items))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DallList DALL-E 任务列表
 | 
			
		||||
func (h *ImageHandler) DallList(c *gin.Context) {
 | 
			
		||||
	var data query
 | 
			
		||||
	if err := c.ShouldBindJSON(&data); err != nil {
 | 
			
		||||
		resp.ERROR(c, types.InvalidArgs)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	session := h.DB.Session(&gorm.Session{})
 | 
			
		||||
	if data.Username != "" {
 | 
			
		||||
		var user model.User
 | 
			
		||||
		err := h.DB.Where("username", data.Username).First(&user).Error
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			session = session.Where("user_id", user.Id)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if data.Prompt != "" {
 | 
			
		||||
		session = session.Where("prompt LIKE ?", "%"+data.Prompt+"%")
 | 
			
		||||
	}
 | 
			
		||||
	if len(data.CreatedAt) == 2 {
 | 
			
		||||
		start := utils.Str2stamp(data.CreatedAt[0] + " 00:00:00")
 | 
			
		||||
		end := utils.Str2stamp(data.CreatedAt[1] + " 00:00:00")
 | 
			
		||||
		session = session.Where("created_at >= ? AND created_at <= ?", start, end)
 | 
			
		||||
	}
 | 
			
		||||
	var total int64
 | 
			
		||||
	session.Model(&model.DallJob{}).Count(&total)
 | 
			
		||||
	var list []model.DallJob
 | 
			
		||||
	var items = make([]vo.DallJob, 0)
 | 
			
		||||
	offset := (data.Page - 1) * data.PageSize
 | 
			
		||||
	err := session.Order("id DESC").Offset(offset).Limit(data.PageSize).Find(&list).Error
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// 填充数据
 | 
			
		||||
		for _, item := range list {
 | 
			
		||||
			var job vo.DallJob
 | 
			
		||||
			err = utils.CopyObject(item, &job)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			job.CreatedAt = item.CreatedAt.Unix()
 | 
			
		||||
			items = append(items, job)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp.SUCCESS(c, vo.NewPage(total, data.Page, data.PageSize, items))
 | 
			
		||||
}
 | 
			
		||||
@@ -539,6 +539,13 @@ func main() {
 | 
			
		||||
				},
 | 
			
		||||
			})
 | 
			
		||||
		}),
 | 
			
		||||
		fx.Provide(admin.NewImageHandler),
 | 
			
		||||
		fx.Invoke(func(s *core.AppServer, h *admin.ImageHandler) {
 | 
			
		||||
			group := s.Engine.Group("/api/admin/image")
 | 
			
		||||
			group.POST("/list/mj", h.MjList)
 | 
			
		||||
			group.POST("/list/sd", h.SdList)
 | 
			
		||||
			group.POST("/list/dall", h.DallList)
 | 
			
		||||
		}),
 | 
			
		||||
	)
 | 
			
		||||
	// 启动应用程序
 | 
			
		||||
	go func() {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,23 +1,21 @@
 | 
			
		||||
package vo
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type MidJourneyJob struct {
 | 
			
		||||
	Id          uint      `json:"id"`
 | 
			
		||||
	Type        string    `json:"type"`
 | 
			
		||||
	UserId      int       `json:"user_id"`
 | 
			
		||||
	ChannelId   string    `json:"channel_id"`
 | 
			
		||||
	TaskId      string    `json:"task_id"`
 | 
			
		||||
	MessageId   string    `json:"message_id"`
 | 
			
		||||
	ReferenceId string    `json:"reference_id"`
 | 
			
		||||
	ImgURL      string    `json:"img_url"`
 | 
			
		||||
	OrgURL      string    `json:"org_url"`
 | 
			
		||||
	Hash        string    `json:"hash"`
 | 
			
		||||
	Progress    int       `json:"progress"`
 | 
			
		||||
	Prompt      string    `json:"prompt"`
 | 
			
		||||
	UseProxy    bool      `json:"use_proxy"`
 | 
			
		||||
	Publish     bool      `json:"publish"`
 | 
			
		||||
	ErrMsg      string    `json:"err_msg"`
 | 
			
		||||
	Power       int       `json:"power"`
 | 
			
		||||
	CreatedAt   time.Time `json:"created_at"`
 | 
			
		||||
	Id          uint   `json:"id"`
 | 
			
		||||
	Type        string `json:"type"`
 | 
			
		||||
	UserId      int    `json:"user_id"`
 | 
			
		||||
	ChannelId   string `json:"channel_id"`
 | 
			
		||||
	TaskId      string `json:"task_id"`
 | 
			
		||||
	MessageId   string `json:"message_id"`
 | 
			
		||||
	ReferenceId string `json:"reference_id"`
 | 
			
		||||
	ImgURL      string `json:"img_url"`
 | 
			
		||||
	OrgURL      string `json:"org_url"`
 | 
			
		||||
	Hash        string `json:"hash"`
 | 
			
		||||
	Progress    int    `json:"progress"`
 | 
			
		||||
	Prompt      string `json:"prompt"`
 | 
			
		||||
	UseProxy    bool   `json:"use_proxy"`
 | 
			
		||||
	Publish     bool   `json:"publish"`
 | 
			
		||||
	ErrMsg      string `json:"err_msg"`
 | 
			
		||||
	Power       int    `json:"power"`
 | 
			
		||||
	CreatedAt   int64  `json:"created_at"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@ package vo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"geekai/core/types"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type SdJob struct {
 | 
			
		||||
@@ -17,5 +16,5 @@ type SdJob struct {
 | 
			
		||||
	Publish   bool               `json:"publish"`
 | 
			
		||||
	ErrMsg    string             `json:"err_msg"`
 | 
			
		||||
	Power     int                `json:"power"`
 | 
			
		||||
	CreatedAt time.Time          `json:"created_at"`
 | 
			
		||||
	CreatedAt int64              `json:"created_at"`
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@
 | 
			
		||||
                  {{ threeItem.title }}
 | 
			
		||||
                </el-menu-item>
 | 
			
		||||
              </el-sub-menu>
 | 
			
		||||
              <el-menu-item v-else :index="subItem.index">
 | 
			
		||||
              <el-menu-item v-else :index="subItem.index" :key="subItem.index">
 | 
			
		||||
                <i v-if="subItem.icon" :class="'iconfont icon-'+subItem.icon"></i>
 | 
			
		||||
                {{ subItem.title }}
 | 
			
		||||
              </el-menu-item>
 | 
			
		||||
@@ -64,8 +64,8 @@ const logo = ref('')
 | 
			
		||||
 | 
			
		||||
// 加载系统配置
 | 
			
		||||
httpGet('/api/admin/config/get?key=system').then(res => {
 | 
			
		||||
  title.value = res.data['admin_title']
 | 
			
		||||
  logo.value = res.data['logo']
 | 
			
		||||
  title.value = res.data.admin_title
 | 
			
		||||
  logo.value = res.data.logo
 | 
			
		||||
}).catch(e => {
 | 
			
		||||
  ElMessage.error("加载系统配置失败: " + e.message)
 | 
			
		||||
})
 | 
			
		||||
@@ -101,7 +101,7 @@ const items = [
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'api-key',
 | 
			
		||||
    index: '/admin/apikey',
 | 
			
		||||
@@ -137,6 +137,11 @@ const items = [
 | 
			
		||||
    index: '/admin/chats',
 | 
			
		||||
    title: '对话管理',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'image',
 | 
			
		||||
    index: '/admin/images',
 | 
			
		||||
    title: '绘图管理',
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    icon: 'role',
 | 
			
		||||
    index: '/admin/manger',
 | 
			
		||||
 
 | 
			
		||||
@@ -233,6 +233,12 @@ const routes = [
 | 
			
		||||
                meta: {title: '对话管理'},
 | 
			
		||||
                component: () => import('@/views/admin/ChatList.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/admin/images',
 | 
			
		||||
                name: 'admin-images',
 | 
			
		||||
                meta: {title: '绘图管理'},
 | 
			
		||||
                component: () => import('@/views/admin/ImageList.vue'),
 | 
			
		||||
            },
 | 
			
		||||
            {
 | 
			
		||||
                path: '/admin/powerLog',
 | 
			
		||||
                name: 'admin-power-log',
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										510
									
								
								web/src/views/admin/ImageList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										510
									
								
								web/src/views/admin/ImageList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,510 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="container chat-list">
 | 
			
		||||
    <el-tabs v-model="activeName" @tab-change="handleChange">
 | 
			
		||||
      <el-tab-pane label="Midjourney" name="mj" v-loading="data.mj.loading">
 | 
			
		||||
        <div class="handle-box">
 | 
			
		||||
          <el-input v-model.number="data.mj.query.username" placeholder="用户名" class="handle-input mr10"
 | 
			
		||||
                    @keyup="search($event,'mj')"></el-input>
 | 
			
		||||
          <el-input v-model="data.mj.query.prompt" placeholder="提示词" class="handle-input mr10"
 | 
			
		||||
                    @keyup="search($event,'mj')"></el-input>
 | 
			
		||||
          <el-date-picker
 | 
			
		||||
              v-model="data.mj.query.created_at"
 | 
			
		||||
              type="daterange"
 | 
			
		||||
              start-placeholder="开始日期"
 | 
			
		||||
              end-placeholder="结束日期"
 | 
			
		||||
              format="YYYY-MM-DD"
 | 
			
		||||
              value-format="YYYY-MM-DD"
 | 
			
		||||
              style="margin-right: 10px;width: 200px; position: relative;top:3px;"
 | 
			
		||||
          />
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchMjData">搜索</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div v-if="data.mj.items.length > 0">
 | 
			
		||||
          <el-row>
 | 
			
		||||
            <el-table :data="data.mj.items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
              <el-table-column prop="user_id" label="用户ID"/>
 | 
			
		||||
              <el-table-column label="任务类型">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-button :color="taskTypeTheme[scope.row.type].color" size="small" plain>{{taskTypeTheme[scope.row.type].text}}</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column prop="progress" label="任务进度">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <span v-if="scope.row.progress <= 100">{{scope.row.progress}}%</span>
 | 
			
		||||
                  <el-tag v-else type="danger">已失败</el-tag>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column prop="power" label="消耗算力"/>
 | 
			
		||||
              <el-table-column label="结果图片">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-button size="small" type="success" @click="showImage(scope.row.img_url)" v-if="scope.row.img_url !== ''" plain>预览图片</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="提示词">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="绘画提示词"
 | 
			
		||||
                      :width="300"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                      :content="scope.row.prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <span>{{ substr(scope.row.prompt, 20) }}</span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="创建时间">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="失败原因">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="失败原因"
 | 
			
		||||
                      :width="300"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                      :content="scope.row.err_msg"
 | 
			
		||||
                      v-if="scope.row.progress === 101"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-text type="danger">{{ substr(scope.row.err_msg, 20) }}</el-text>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
                  <span v-else>无</span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="操作" width="180">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row, 'mj')">
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popconfirm>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <div class="pagination">
 | 
			
		||||
            <el-pagination v-if="data.mj.total > 0" background
 | 
			
		||||
                           layout="total,prev, pager, next"
 | 
			
		||||
                           :hide-on-single-page="true"
 | 
			
		||||
                           v-model:current-page="data.mj.page"
 | 
			
		||||
                           v-model:page-size="data.mj.pageSize"
 | 
			
		||||
                           @current-change="fetchMjData()"
 | 
			
		||||
                           :total="data.mj.total"/>
 | 
			
		||||
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-empty v-else />
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
      <el-tab-pane label="Stable-Diffusion" name="sd" v-loading="data.sd.loading">
 | 
			
		||||
        <div class="handle-box">
 | 
			
		||||
          <el-input v-model.number="data.sd.query.username" placeholder="用户名" class="handle-input mr10"
 | 
			
		||||
                    @keyup="search($event, 'sd')"></el-input>
 | 
			
		||||
          <el-input v-model="data.sd.query.prompt" placeholder="提示词" class="handle-input mr10"
 | 
			
		||||
                    @keyup="search($event, 'sd')"></el-input>
 | 
			
		||||
          <el-date-picker
 | 
			
		||||
              v-model="data.sd.query.created_at"
 | 
			
		||||
              type="daterange"
 | 
			
		||||
              start-placeholder="开始日期"
 | 
			
		||||
              end-placeholder="结束日期"
 | 
			
		||||
              format="YYYY-MM-DD"
 | 
			
		||||
              value-format="YYYY-MM-DD"
 | 
			
		||||
              style="margin-right: 10px;width: 200px; position: relative;top:3px;"
 | 
			
		||||
          />
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchSdData">搜索</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div v-if="data.sd.items.length > 0">
 | 
			
		||||
          <el-row>
 | 
			
		||||
            <el-table :data="data.sd.items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
              <el-table-column prop="user_id" label="用户ID"/>
 | 
			
		||||
              <el-table-column prop="progress" label="任务进度">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <span v-if="scope.row.progress <= 100">{{scope.row.progress}}%</span>
 | 
			
		||||
                  <el-tag v-else type="danger">已失败</el-tag>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column prop="power" label="消耗算力"/>
 | 
			
		||||
              <el-table-column label="结果图片">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-button size="small" type="success" @click="showImage(scope.row.img_url)" v-if="scope.row.img_url !== ''" plain>预览图片</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="提示词">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="绘画提示词"
 | 
			
		||||
                      :width="300"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                      :content="scope.row.prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <span>{{ substr(scope.row.prompt, 20) }}</span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="创建时间">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="失败原因">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="失败原因"
 | 
			
		||||
                      :width="300"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                      :content="scope.row.err_msg"
 | 
			
		||||
                      v-if="scope.row.progress === 101"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-text type="danger">{{ substr(scope.row.err_msg, 20) }}</el-text>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
                  <span v-else>无</span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="操作" width="180">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row, 'sd')">
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popconfirm>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <div class="pagination">
 | 
			
		||||
            <el-pagination v-if="data.sd.total > 0" background
 | 
			
		||||
                           layout="total,prev, pager, next"
 | 
			
		||||
                           :hide-on-single-page="true"
 | 
			
		||||
                           v-model:current-page="data.sd.page"
 | 
			
		||||
                           v-model:page-size="data.sd.pageSize"
 | 
			
		||||
                           @current-change="fetchSdData()"
 | 
			
		||||
                           :total="data.sd.total"/>
 | 
			
		||||
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-empty v-else />
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
      <el-tab-pane label="DALL-E" name="dall">
 | 
			
		||||
        <div class="handle-box">
 | 
			
		||||
          <el-input v-model.number="data.dall.query.username" placeholder="用户名" class="handle-input mr10"
 | 
			
		||||
                    @keyup="search($event,'dall')"></el-input>
 | 
			
		||||
          <el-input v-model="data.dall.query.prompt" placeholder="提示词" class="handle-input mr10"
 | 
			
		||||
                    @keyup="search($event, 'dall')"></el-input>
 | 
			
		||||
          <el-date-picker
 | 
			
		||||
              v-model="data.dall.query.created_at"
 | 
			
		||||
              type="daterange"
 | 
			
		||||
              start-placeholder="开始日期"
 | 
			
		||||
              end-placeholder="结束日期"
 | 
			
		||||
              format="YYYY-MM-DD"
 | 
			
		||||
              value-format="YYYY-MM-DD"
 | 
			
		||||
              style="margin-right: 10px;width: 200px; position: relative;top:3px;"
 | 
			
		||||
          />
 | 
			
		||||
          <el-button type="primary" :icon="Search" @click="fetchDallData">搜索</el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
        <div v-if="data.dall.items.length > 0">
 | 
			
		||||
          <el-row>
 | 
			
		||||
            <el-table :data="data.dall.items" :row-key="row => row.id" table-layout="auto">
 | 
			
		||||
              <el-table-column prop="user_id" label="用户ID"/>
 | 
			
		||||
              <el-table-column prop="progress" label="任务进度">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <span v-if="scope.row.progress <= 100">{{scope.row.progress}}%</span>
 | 
			
		||||
                  <el-tag v-else type="danger">已失败</el-tag>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column prop="power" label="消耗算力"/>
 | 
			
		||||
              <el-table-column label="结果图片">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-button size="small" type="success" @click="showImage(scope.row.img_url)" v-if="scope.row.img_url !== ''" plain>预览图片</el-button>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="提示词">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="绘画提示词"
 | 
			
		||||
                      :width="300"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                      :content="scope.row.prompt"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <span>{{ substr(scope.row.prompt, 20) }}</span>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="创建时间">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <span>{{ dateFormat(scope.row['created_at']) }}</span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="失败原因">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popover
 | 
			
		||||
                      placement="top-start"
 | 
			
		||||
                      title="失败原因"
 | 
			
		||||
                      :width="300"
 | 
			
		||||
                      trigger="hover"
 | 
			
		||||
                      :content="scope.row.err_msg"
 | 
			
		||||
                      v-if="scope.row.progress === 101"
 | 
			
		||||
                  >
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-text type="danger">{{ substr(scope.row.err_msg, 20) }}</el-text>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popover>
 | 
			
		||||
                  <span v-else>无</span>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
              <el-table-column label="操作" width="180">
 | 
			
		||||
                <template #default="scope">
 | 
			
		||||
                  <el-popconfirm title="确定要删除当前记录吗?" @confirm="remove(scope.row, 'dall')">
 | 
			
		||||
                    <template #reference>
 | 
			
		||||
                      <el-button size="small" type="danger">删除</el-button>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-popconfirm>
 | 
			
		||||
                </template>
 | 
			
		||||
              </el-table-column>
 | 
			
		||||
            </el-table>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <div class="pagination">
 | 
			
		||||
            <el-pagination v-if="data.dall.total > 0" background
 | 
			
		||||
                           layout="total,prev, pager, next"
 | 
			
		||||
                           :hide-on-single-page="true"
 | 
			
		||||
                           v-model:current-page="data.dall.page"
 | 
			
		||||
                           v-model:page-size="data.dall.pageSize"
 | 
			
		||||
                           @current-change="fetchDallData()"
 | 
			
		||||
                           :total="data.dall.total"/>
 | 
			
		||||
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-empty v-else />
 | 
			
		||||
      </el-tab-pane>
 | 
			
		||||
    </el-tabs>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="showImageDialog"
 | 
			
		||||
        title="图片预览"
 | 
			
		||||
    >
 | 
			
		||||
      <el-image
 | 
			
		||||
          :src="imgURL"
 | 
			
		||||
          :zoom-rate="1.2"
 | 
			
		||||
          :max-scale="7"
 | 
			
		||||
          :min-scale="0.2"
 | 
			
		||||
          :preview-src-list="[imgURL]"
 | 
			
		||||
          :initial-index="0"
 | 
			
		||||
          fit="cover"
 | 
			
		||||
      />
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {onMounted, ref} from "vue";
 | 
			
		||||
import {httpGet, httpPost} from "@/utils/http";
 | 
			
		||||
import {ElMessage} from "element-plus";
 | 
			
		||||
import {dateFormat, substr} from "@/utils/libs";
 | 
			
		||||
import {Search} from "@element-plus/icons-vue";
 | 
			
		||||
 | 
			
		||||
// 变量定义
 | 
			
		||||
const data = ref({
 | 
			
		||||
  "mj": {
 | 
			
		||||
    items: [],
 | 
			
		||||
    query: {prompt: "", username: "", created_at: [], page: 1, page_size: 15},
 | 
			
		||||
    total: 0,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 15,
 | 
			
		||||
    loading: true
 | 
			
		||||
  },
 | 
			
		||||
  "sd": {
 | 
			
		||||
    items: [],
 | 
			
		||||
    query: {prompt: "", username: "", created_at: [], page: 1, page_size: 15},
 | 
			
		||||
    total: 0,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 15,
 | 
			
		||||
    loading: true
 | 
			
		||||
  },
 | 
			
		||||
  "dall": {
 | 
			
		||||
    items: [],
 | 
			
		||||
    query: {prompt: "", username: "", created_at: [], page: 1, page_size: 15},
 | 
			
		||||
    total: 0,
 | 
			
		||||
    page: 1,
 | 
			
		||||
    pageSize: 15,
 | 
			
		||||
    loading: true
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
const activeName = ref("mj")
 | 
			
		||||
const taskTypeTheme = {
 | 
			
		||||
  image: {text: "绘图", color: "#2185d0"},
 | 
			
		||||
  upscale: {text: "放大", color: "#f2711c" },
 | 
			
		||||
  variation: {text: "变换", color: "#00b5ad"},
 | 
			
		||||
  blend: {text: "融图", color: "#21ba45"},
 | 
			
		||||
  swapFace: {text: "换脸", color: "#a333c8"}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  fetchMjData()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
const handleChange = (tab) => {
 | 
			
		||||
  switch (tab) {
 | 
			
		||||
    case "mj":
 | 
			
		||||
      fetchMjData()
 | 
			
		||||
      break
 | 
			
		||||
    case "sd":
 | 
			
		||||
      fetchSdData()
 | 
			
		||||
      break
 | 
			
		||||
    case "dall":
 | 
			
		||||
      fetchDallData()
 | 
			
		||||
      break
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 搜索对话
 | 
			
		||||
const search = (evt,tab) => {
 | 
			
		||||
  if (evt.keyCode === 13) {
 | 
			
		||||
    handleChange(tab)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取数据
 | 
			
		||||
const fetchMjData = () => {
 | 
			
		||||
  const d = data.value.mj
 | 
			
		||||
  d.query.page = d.page
 | 
			
		||||
  d.query.page_size = d.pageSize
 | 
			
		||||
  httpPost('/api/admin/image/list/mj', d.query).then((res) => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      d.items = res.data.items
 | 
			
		||||
      d.total = res.data.total
 | 
			
		||||
      d.page = res.data.page
 | 
			
		||||
      d.pageSize = res.data.page_size
 | 
			
		||||
    }
 | 
			
		||||
    d.loading = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchSdData = () => {
 | 
			
		||||
  const d = data.value.sd
 | 
			
		||||
  d.query.page = d.page
 | 
			
		||||
  d.query.page_size = d.pageSize
 | 
			
		||||
  httpPost('/api/admin/image/list/sd', d.query).then((res) => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      d.items = res.data.items
 | 
			
		||||
      d.total = res.data.total
 | 
			
		||||
      d.page = res.data.page
 | 
			
		||||
      d.pageSize = res.data.page_size
 | 
			
		||||
    }
 | 
			
		||||
    d.loading = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fetchDallData = () => {
 | 
			
		||||
  const d = data.value.dall
 | 
			
		||||
  d.query.page = d.page
 | 
			
		||||
  d.query.page_size = d.pageSize
 | 
			
		||||
  httpPost('/api/admin/image/list/dall', d.query).then((res) => {
 | 
			
		||||
    if (res.data) {
 | 
			
		||||
      d.items = res.data.items
 | 
			
		||||
      d.total = res.data.total
 | 
			
		||||
      d.page = res.data.page
 | 
			
		||||
      d.pageSize = res.data.page_size
 | 
			
		||||
    }
 | 
			
		||||
    d.loading = false
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessage.error("获取数据失败:" + e.message);
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const remove = function (row,tab) {
 | 
			
		||||
  httpGet('/api/admin/chat/remove?chat_id=' + row.chat_id).then(() => {
 | 
			
		||||
    ElMessage.success("删除成功!")
 | 
			
		||||
  }).catch((e) => {
 | 
			
		||||
    ElMessage.error("删除失败:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const showImageDialog = ref(false)
 | 
			
		||||
const imgURL = ref('')
 | 
			
		||||
const showImage = (url) => {
 | 
			
		||||
  showImageDialog.value = true
 | 
			
		||||
  imgURL.value = url
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.chat-list {
 | 
			
		||||
  .handle-box {
 | 
			
		||||
    margin-bottom 20px
 | 
			
		||||
    .handle-input {
 | 
			
		||||
      max-width 150px;
 | 
			
		||||
      margin-right 10px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .opt-box {
 | 
			
		||||
    padding-bottom: 10px;
 | 
			
		||||
    display flex;
 | 
			
		||||
    justify-content flex-end
 | 
			
		||||
 | 
			
		||||
    .el-icon {
 | 
			
		||||
      margin-right: 5px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-select {
 | 
			
		||||
    width: 100%
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .pagination {
 | 
			
		||||
    padding 20px 0
 | 
			
		||||
    display flex
 | 
			
		||||
    justify-content right
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .chat-box {
 | 
			
		||||
    overflow hidden
 | 
			
		||||
 | 
			
		||||
    // 变量定义
 | 
			
		||||
    --content-font-size: 16px;
 | 
			
		||||
    --content-color: #c1c1c1;
 | 
			
		||||
 | 
			
		||||
    font-family: 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
 | 
			
		||||
 | 
			
		||||
    .chat-line {
 | 
			
		||||
      // 隐藏滚动条
 | 
			
		||||
 | 
			
		||||
      ::-webkit-scrollbar {
 | 
			
		||||
        width: 0;
 | 
			
		||||
        height: 0;
 | 
			
		||||
        background-color: transparent;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: flex-start;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
		Reference in New Issue
	
	Block a user