mirror of
				https://github.com/yangjian102621/geekai.git
				synced 2025-11-04 08:13:43 +08:00 
			
		
		
		
	wechat login is ready
This commit is contained in:
		@@ -7,7 +7,7 @@
 | 
			
		||||
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
 | 
			
		||||
* 功能优化:优化聊天界面的用户上传文件的列表样式
 | 
			
		||||
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
 | 
			
		||||
* 功能新增:支持微信等社交媒体登录
 | 
			
		||||
* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## v4.0.9
 | 
			
		||||
 
 | 
			
		||||
@@ -446,14 +446,9 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
 | 
			
		||||
			// discord 服务器图片需要使用代理转发图片数据流
 | 
			
		||||
			if strings.HasPrefix(item.OrgURL, "https://cdn.discordapp.com") {
 | 
			
		||||
				image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
 | 
			
		||||
				if err == nil {
 | 
			
		||||
					job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				job.ImgURL = job.OrgURL
 | 
			
		||||
			image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
 | 
			
		||||
			if err == nil {
 | 
			
		||||
				job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -285,9 +285,99 @@ func (h *UserHandler) CLoginRequest(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
// CLoginCallback 第三方登录回调
 | 
			
		||||
func (h *UserHandler) CLoginCallback(c *gin.Context) {
 | 
			
		||||
	//platform := h.GetTrim(c, "type")
 | 
			
		||||
	//code := h.GetTrim(c, "code")
 | 
			
		||||
	loginType := h.GetTrim(c, "login_type")
 | 
			
		||||
	code := h.GetTrim(c, "code")
 | 
			
		||||
 | 
			
		||||
	var res types.BizVo
 | 
			
		||||
	apiURL := fmt.Sprintf("%s/api/clogin/info", h.App.Config.ApiConfig.ApiURL)
 | 
			
		||||
	r, err := req.C().R().SetBody(gin.H{"login_type": loginType, "code": code}).
 | 
			
		||||
		SetHeader("AppId", h.App.Config.ApiConfig.AppId).
 | 
			
		||||
		SetHeader("Authorization", fmt.Sprintf("Bearer %s", h.App.Config.ApiConfig.Token)).
 | 
			
		||||
		SetSuccessResult(&res).
 | 
			
		||||
		Post(apiURL)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if r.IsErrorState() {
 | 
			
		||||
		resp.ERROR(c, "error with login http status: "+r.Status)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if res.Code != types.Success {
 | 
			
		||||
		resp.ERROR(c, "error with http response: "+res.Message)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// login successfully
 | 
			
		||||
	data := res.Data.(map[string]interface{})
 | 
			
		||||
	session := gin.H{}
 | 
			
		||||
	var user model.User
 | 
			
		||||
	tx := h.DB.Debug().Where("openid", data["openid"]).First(&user)
 | 
			
		||||
	if tx.Error != nil { // user not exist, create new user
 | 
			
		||||
		// 检测最大注册人数
 | 
			
		||||
		var totalUser int64
 | 
			
		||||
		h.DB.Model(&model.User{}).Count(&totalUser)
 | 
			
		||||
		if h.licenseService.GetLicense().Configs.UserNum > 0 && int(totalUser) >= h.licenseService.GetLicense().Configs.UserNum {
 | 
			
		||||
			resp.ERROR(c, "当前注册用户数已达上限,请请升级 License")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		salt := utils.RandString(8)
 | 
			
		||||
		password := fmt.Sprintf("%d", utils.RandomNumber(8))
 | 
			
		||||
		user = model.User{
 | 
			
		||||
			Username:   fmt.Sprintf("%s@%d", loginType, utils.RandomNumber(10)),
 | 
			
		||||
			Password:   utils.GenPassword(password, salt),
 | 
			
		||||
			Avatar:     fmt.Sprintf("%s", data["avatar"]),
 | 
			
		||||
			Salt:       salt,
 | 
			
		||||
			Status:     true,
 | 
			
		||||
			ChatRoles:  utils.JsonEncode([]string{"gpt"}),               // 默认只订阅通用助手角色
 | 
			
		||||
			ChatModels: utils.JsonEncode(h.App.SysConfig.DefaultModels), // 默认开通的模型
 | 
			
		||||
			Power:      h.App.SysConfig.InitPower,
 | 
			
		||||
			OpenId:     fmt.Sprintf("%s", data["openid"]),
 | 
			
		||||
			Nickname:   fmt.Sprintf("%s", data["nickname"]),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		tx = h.DB.Create(&user)
 | 
			
		||||
		if tx.Error != nil {
 | 
			
		||||
			resp.ERROR(c, "保存数据失败")
 | 
			
		||||
			logger.Error(tx.Error)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		session["username"] = user.Username
 | 
			
		||||
		session["password"] = password
 | 
			
		||||
	} else { // login directly
 | 
			
		||||
		// 更新最后登录时间和IP
 | 
			
		||||
		user.LastLoginIp = c.ClientIP()
 | 
			
		||||
		user.LastLoginAt = time.Now().Unix()
 | 
			
		||||
		h.DB.Model(&user).Updates(user)
 | 
			
		||||
 | 
			
		||||
		h.DB.Create(&model.UserLoginLog{
 | 
			
		||||
			UserId:       user.Id,
 | 
			
		||||
			Username:     user.Username,
 | 
			
		||||
			LoginIp:      c.ClientIP(),
 | 
			
		||||
			LoginAddress: utils.Ip2Region(h.searcher, c.ClientIP()),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 创建 token
 | 
			
		||||
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
 | 
			
		||||
		"user_id": user.Id,
 | 
			
		||||
		"expired": time.Now().Add(time.Second * time.Duration(h.App.Config.Session.MaxAge)).Unix(),
 | 
			
		||||
	})
 | 
			
		||||
	tokenString, err := token.SignedString([]byte(h.App.Config.Session.SecretKey))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		resp.ERROR(c, "Failed to generate token, "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// 保存到 redis
 | 
			
		||||
	key := fmt.Sprintf("users/%d", user.Id)
 | 
			
		||||
	if _, err := h.redis.Set(c, key, tokenString, 0).Result(); err != nil {
 | 
			
		||||
		resp.ERROR(c, "error with save token: "+err.Error())
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	session["token"] = tokenString
 | 
			
		||||
	resp.SUCCESS(c, session)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Session 获取/验证会话
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,6 @@
 | 
			
		||||
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
 | 
			
		||||
ALTER TABLE `chatgpt_users` ADD `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
 | 
			
		||||
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
 | 
			
		||||
ALTER TABLE `chatgpt_users` ADD UNIQUE(`openid`);
 | 
			
		||||
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `openid`;
 | 
			
		||||
ALTER TABLE `chatgpt_users` CHANGE `avatar` `avatar` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '头像';
 | 
			
		||||
ALTER TABLE `chatgpt_chat_history` CHANGE `icon` `icon` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '角色图标';
 | 
			
		||||
							
								
								
									
										115
									
								
								web/src/assets/css/login.styl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								web/src/assets/css/login.styl
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
			
		||||
.bg {
 | 
			
		||||
  position fixed
 | 
			
		||||
  left 0
 | 
			
		||||
  right 0
 | 
			
		||||
  top 0
 | 
			
		||||
  bottom 0
 | 
			
		||||
  background-color #313237
 | 
			
		||||
  background-image url("~@/assets/img/login-bg.jpg")
 | 
			
		||||
  background-size cover
 | 
			
		||||
  background-position center
 | 
			
		||||
  background-repeat repeat-y
 | 
			
		||||
  //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
  .contain {
 | 
			
		||||
    position fixed
 | 
			
		||||
    left 50%
 | 
			
		||||
    top 40%
 | 
			
		||||
    width 90%
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
    transform translate(-50%, -50%)
 | 
			
		||||
    padding 20px 10px;
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    border-radius 10px;
 | 
			
		||||
 | 
			
		||||
    .logo {
 | 
			
		||||
      text-align center
 | 
			
		||||
 | 
			
		||||
      .el-image {
 | 
			
		||||
        width 120px;
 | 
			
		||||
        cursor pointer
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .header {
 | 
			
		||||
      width 100%
 | 
			
		||||
      margin-bottom 24px
 | 
			
		||||
      font-size 24px
 | 
			
		||||
      color $white_v1
 | 
			
		||||
      letter-space 2px
 | 
			
		||||
      text-align center
 | 
			
		||||
      padding-top 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      width 100%
 | 
			
		||||
      height: auto
 | 
			
		||||
      border-radius 3px
 | 
			
		||||
 | 
			
		||||
      .block {
 | 
			
		||||
        margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
        .el-input__inner {
 | 
			
		||||
          border 1px solid $gray-v6 !important
 | 
			
		||||
 | 
			
		||||
          .el-icon-user, .el-icon-lock {
 | 
			
		||||
            font-size 20px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .btn-row {
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
 | 
			
		||||
        .login-btn {
 | 
			
		||||
          width 100%
 | 
			
		||||
          font-size 16px
 | 
			
		||||
          letter-spacing 2px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .text-line {
 | 
			
		||||
        justify-content center
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
        font-size 14px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .opt {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        .el-col {
 | 
			
		||||
          text-align center
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .divider {
 | 
			
		||||
        border-top: 2px solid #c1c1c1;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .clogin {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        display flex
 | 
			
		||||
        justify-content center
 | 
			
		||||
 | 
			
		||||
        .iconfont {
 | 
			
		||||
          font-size 20px
 | 
			
		||||
          background: #E9F1F6;
 | 
			
		||||
          padding: 8px;
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
        }
 | 
			
		||||
        .iconfont.icon-wechat {
 | 
			
		||||
          color #0bc15f
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .footer {
 | 
			
		||||
    color #ffffff;
 | 
			
		||||
 | 
			
		||||
    .container {
 | 
			
		||||
      padding 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -155,6 +155,7 @@ const synthesis = (text) => {
 | 
			
		||||
 | 
			
		||||
// 重新生成
 | 
			
		||||
const reGenerate = (prompt) => {
 | 
			
		||||
  console.log(prompt)
 | 
			
		||||
  emits('regen', prompt)
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								web/src/components/TaskList.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								web/src/components/TaskList.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="running-job-list">
 | 
			
		||||
    <div class="running-job-box" v-if="list.length > 0">
 | 
			
		||||
      <div class="job-item" v-for="item in list">
 | 
			
		||||
        <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
          <el-image v-if="item.img_url" :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
            <template #placeholder>
 | 
			
		||||
              <div class="image-slot">
 | 
			
		||||
                正在加载图片
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
 | 
			
		||||
            <template #error>
 | 
			
		||||
              <div class="image-slot">
 | 
			
		||||
                <el-icon>
 | 
			
		||||
                  <Picture/>
 | 
			
		||||
                </el-icon>
 | 
			
		||||
              </div>
 | 
			
		||||
            </template>
 | 
			
		||||
          </el-image>
 | 
			
		||||
 | 
			
		||||
          <div class="progress">
 | 
			
		||||
            <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                         color="#47fff1"/>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-image fit="cover" v-else>
 | 
			
		||||
          <template #error>
 | 
			
		||||
            <div class="image-slot">
 | 
			
		||||
              <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
              <span>任务正在排队中</span>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
        </el-image>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <el-empty :image-size="100" v-else/>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue";
 | 
			
		||||
import {CircleCloseFilled, Picture} from "@element-plus/icons-vue";
 | 
			
		||||
import {isImage, removeArrayItem, substr} from "@/utils/libs";
 | 
			
		||||
import {FormatFileSize, GetFileIcon, GetFileType} from "@/store/system";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  list: {
 | 
			
		||||
    type: Array,
 | 
			
		||||
    default:[],
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style scoped lang="stylus">
 | 
			
		||||
@import "~@/assets/css/running-job-list.styl"
 | 
			
		||||
</style>
 | 
			
		||||
@@ -101,6 +101,12 @@ const routes = [
 | 
			
		||||
        meta: {title: '用户登录'},
 | 
			
		||||
        component: () => import('@/views/Login.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: 'login-callback',
 | 
			
		||||
        path: '/login/callback',
 | 
			
		||||
        meta: {title: '用户登录'},
 | 
			
		||||
        component: () => import('@/views/LoginCallback.vue'),
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: 'register',
 | 
			
		||||
        path: '/register',
 | 
			
		||||
 
 | 
			
		||||
@@ -626,10 +626,12 @@ const connect = function (chat_id, role_id) {
 | 
			
		||||
        reader.onload = () => {
 | 
			
		||||
          const data = JSON.parse(String(reader.result));
 | 
			
		||||
          if (data.type === 'start') {
 | 
			
		||||
            const prePrompt = chatData.value[chatData.value.length-1].content
 | 
			
		||||
            chatData.value.push({
 | 
			
		||||
              type: "reply",
 | 
			
		||||
              id: randString(32),
 | 
			
		||||
              icon: _role['icon'],
 | 
			
		||||
              prompt:prePrompt,
 | 
			
		||||
              content: ""
 | 
			
		||||
            });
 | 
			
		||||
          } else if (data.type === 'end') { // 消息接收完毕
 | 
			
		||||
@@ -864,7 +866,7 @@ const reGenerate = function (prompt) {
 | 
			
		||||
    type: "prompt",
 | 
			
		||||
    id: randString(32),
 | 
			
		||||
    icon: loginUser.value.avatar,
 | 
			
		||||
    content: md.render(text)
 | 
			
		||||
    content: text
 | 
			
		||||
  });
 | 
			
		||||
  socket.value.send(JSON.stringify({type: "chat", content: prompt}));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -86,43 +86,7 @@
 | 
			
		||||
          <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
            <div class="job-list-box">
 | 
			
		||||
              <h2>任务列表</h2>
 | 
			
		||||
              <div class="running-job-list">
 | 
			
		||||
                <div class="running-job-box" v-if="runningJobs.length > 0">
 | 
			
		||||
                  <div class="job-item" v-for="item in runningJobs" :key="item.id">
 | 
			
		||||
                    <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
                      <el-image :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
                        <template #placeholder>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            正在加载图片
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            <el-icon>
 | 
			
		||||
                              <Picture/>
 | 
			
		||||
                            </el-icon>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
 | 
			
		||||
                      <div class="progress">
 | 
			
		||||
                        <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                                     color="#47fff1"/>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <el-image fit="cover" v-else>
 | 
			
		||||
                      <template #error>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                          <span>任务正在排队中</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-image>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-empty :image-size="100" v-else/>
 | 
			
		||||
              </div>
 | 
			
		||||
              <task-list :list="runningJobs" />
 | 
			
		||||
 | 
			
		||||
              <h2>创作记录</h2>
 | 
			
		||||
              <div class="finish-job-list">
 | 
			
		||||
@@ -228,6 +192,7 @@ import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
 | 
			
		||||
import Clipboard from "clipboard";
 | 
			
		||||
import {checkSession} from "@/action/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
 
 | 
			
		||||
@@ -215,10 +215,11 @@ const init = () => {
 | 
			
		||||
const logout = function () {
 | 
			
		||||
  httpGet('/api/user/logout').then(() => {
 | 
			
		||||
    removeUserToken()
 | 
			
		||||
    store.setShowLoginDialog(true)
 | 
			
		||||
    loginUser.value = {}
 | 
			
		||||
    // 刷新组件
 | 
			
		||||
    routerViewKey.value += 1
 | 
			
		||||
    router.push("/login")
 | 
			
		||||
    // store.setShowLoginDialog(true)
 | 
			
		||||
    // loginUser.value = {}
 | 
			
		||||
    // // 刷新组件
 | 
			
		||||
    // routerViewKey.value += 1
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
    ElMessage.error('注销失败!');
 | 
			
		||||
  })
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,7 @@
 | 
			
		||||
          </el-form>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div class="task-list-box" @scrollend="handleScrollEnd">
 | 
			
		||||
      <div class="task-list-box">
 | 
			
		||||
        <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
          <div class="extra-params">
 | 
			
		||||
            <el-form>
 | 
			
		||||
@@ -450,43 +450,7 @@
 | 
			
		||||
 | 
			
		||||
          <div class="job-list-box">
 | 
			
		||||
            <h2>任务列表</h2>
 | 
			
		||||
            <div class="running-job-list">
 | 
			
		||||
              <div class="running-job-box" v-if="runningJobs.length > 0">
 | 
			
		||||
                <div class="job-item" v-for="item in runningJobs">
 | 
			
		||||
                  <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
                    <el-image :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
                      <template #placeholder>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          正在加载图片
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
 | 
			
		||||
                      <template #error>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          <el-icon>
 | 
			
		||||
                            <Picture/>
 | 
			
		||||
                          </el-icon>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-image>
 | 
			
		||||
 | 
			
		||||
                    <div class="progress">
 | 
			
		||||
                      <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                                   color="#47fff1"/>
 | 
			
		||||
                    </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <el-image fit="cover" v-else>
 | 
			
		||||
                    <template #error>
 | 
			
		||||
                      <div class="image-slot">
 | 
			
		||||
                        <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                        <span>任务正在排队中</span>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </template>
 | 
			
		||||
                  </el-image>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
              <el-empty :image-size="100" v-else/>
 | 
			
		||||
            </div>
 | 
			
		||||
            <task-list :list="runningJobs" />
 | 
			
		||||
 | 
			
		||||
            <h2>创作记录</h2>
 | 
			
		||||
            <div class="finish-job-list">
 | 
			
		||||
@@ -617,6 +581,7 @@ import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {copyObj, removeArrayItem} from "@/utils/libs";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
const paramBoxHeight = ref(0)
 | 
			
		||||
 
 | 
			
		||||
@@ -296,39 +296,8 @@
 | 
			
		||||
          <div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
 | 
			
		||||
            <div class="job-list-box">
 | 
			
		||||
              <h2>任务列表</h2>
 | 
			
		||||
              <div class="running-job-list">
 | 
			
		||||
                <div class="running-job-box" v-if="runningJobs.length > 0">
 | 
			
		||||
                  <div class="job-item" v-for="item in runningJobs">
 | 
			
		||||
                    <div v-if="item.progress > 0" class="job-item-inner">
 | 
			
		||||
                      <el-image :src="item['img_url']" fit="cover" loading="lazy">
 | 
			
		||||
                        <template #placeholder>
 | 
			
		||||
                          <div class="image-slot">
 | 
			
		||||
                            正在加载图片
 | 
			
		||||
                          </div>
 | 
			
		||||
                        </template>
 | 
			
		||||
 | 
			
		||||
                        <template #error>
 | 
			
		||||
                          <div class="image-slot"></div>
 | 
			
		||||
                        </template>
 | 
			
		||||
                      </el-image>
 | 
			
		||||
 | 
			
		||||
                      <div class="progress">
 | 
			
		||||
                        <el-progress type="circle" :percentage="item.progress" :width="100"
 | 
			
		||||
                                     color="#47fff1"/>
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <el-image fit="cover" v-else>
 | 
			
		||||
                      <template #error>
 | 
			
		||||
                        <div class="image-slot">
 | 
			
		||||
                          <i class="iconfont icon-quick-start"></i>
 | 
			
		||||
                          <span>任务正在排队中</span>
 | 
			
		||||
                        </div>
 | 
			
		||||
                      </template>
 | 
			
		||||
                    </el-image>
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <el-empty :image-size="100" v-else/>
 | 
			
		||||
              </div>
 | 
			
		||||
              <task-list :list="runningJobs" />
 | 
			
		||||
              
 | 
			
		||||
              <h2>创作记录</h2>
 | 
			
		||||
              <div class="finish-job-list">
 | 
			
		||||
                <div v-if="finishedJobs.length > 0">
 | 
			
		||||
@@ -506,6 +475,7 @@ import {checkSession} from "@/action/session";
 | 
			
		||||
import {useRouter} from "vue-router";
 | 
			
		||||
import {getSessionId} from "@/store/session";
 | 
			
		||||
import {useSharedStore} from "@/store/sharedata";
 | 
			
		||||
import TaskList from "@/components/TaskList.vue";
 | 
			
		||||
 | 
			
		||||
const listBoxHeight = ref(0)
 | 
			
		||||
// const paramBoxHeight = ref(0)
 | 
			
		||||
 
 | 
			
		||||
@@ -35,19 +35,22 @@
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <el-row class="opt" :gutter="24">
 | 
			
		||||
            <el-col :span="span" v-if="cLoginURL !== ''">
 | 
			
		||||
              <el-tooltip class="item" effect="light" content="微信扫码登录" placement="top">
 | 
			
		||||
                <a class="wechat-login" :href="cLoginURL"><i class="iconfont icon-wechat"></i></a>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="span"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
 | 
			
		||||
            <el-col :span="span">
 | 
			
		||||
            <el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
              <el-link type="info" @click="showResetPass = true">重置密码</el-link>
 | 
			
		||||
            </el-col>
 | 
			
		||||
            <el-col :span="span">
 | 
			
		||||
            <el-col :span="8">
 | 
			
		||||
              <el-link type="info" @click="router.push('/')">首页</el-link>
 | 
			
		||||
            </el-col>
 | 
			
		||||
          </el-row>
 | 
			
		||||
 | 
			
		||||
          <div v-if="wechatLoginURL !== ''">
 | 
			
		||||
            <el-divider class="divider">其他登录方式</el-divider>
 | 
			
		||||
 | 
			
		||||
            <div class="clogin">
 | 
			
		||||
              <a class="wechat-login" :href="wechatLoginURL"><i class="iconfont icon-wechat"></i></a>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
@@ -80,8 +83,7 @@ const password = ref(process.env.VUE_APP_PASS);
 | 
			
		||||
const showResetPass = ref(false)
 | 
			
		||||
const logo = ref("/images/logo.png")
 | 
			
		||||
const licenseConfig = ref({})
 | 
			
		||||
const cLoginURL = ref('')
 | 
			
		||||
const span = ref(8)
 | 
			
		||||
const wechatLoginURL = ref('')
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  // 获取系统配置
 | 
			
		||||
@@ -94,7 +96,6 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
  httpGet("/api/config/license").then(res => {
 | 
			
		||||
    licenseConfig.value = res.data
 | 
			
		||||
    span.value = 6
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    showMessageError("获取 License 配置:" + e.message)
 | 
			
		||||
  })
 | 
			
		||||
@@ -108,10 +109,9 @@ onMounted(() => {
 | 
			
		||||
  }).catch(() => {
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  // const returnURL = `${location.protocol}//${location.host}/user/api/clogin/callback`
 | 
			
		||||
  const returnURL = `https://ai.r9it.com/user/api/clogin/callback`
 | 
			
		||||
  const returnURL = `${location.protocol}//${location.host}/login/callback`
 | 
			
		||||
  httpGet("/api/user/clogin/request?return_url="+returnURL).then(res => {
 | 
			
		||||
    cLoginURL.value = res.data.url
 | 
			
		||||
    wechatLoginURL.value = res.data.url
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    console.error(e)
 | 
			
		||||
  })
 | 
			
		||||
@@ -147,103 +147,5 @@ const login = function () {
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.bg {
 | 
			
		||||
  position fixed
 | 
			
		||||
  left 0
 | 
			
		||||
  right 0
 | 
			
		||||
  top 0
 | 
			
		||||
  bottom 0
 | 
			
		||||
  background-color #313237
 | 
			
		||||
  background-image url("~@/assets/img/login-bg.jpg")
 | 
			
		||||
  background-size cover
 | 
			
		||||
  background-position center
 | 
			
		||||
  background-repeat repeat-y
 | 
			
		||||
  //filter: blur(10px); /* 调整模糊程度,可以根据需要修改值 */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main {
 | 
			
		||||
  .contain {
 | 
			
		||||
    position fixed
 | 
			
		||||
    left 50%
 | 
			
		||||
    top 40%
 | 
			
		||||
    width 90%
 | 
			
		||||
    max-width 400px;
 | 
			
		||||
    transform translate(-50%, -50%)
 | 
			
		||||
    padding 20px 10px;
 | 
			
		||||
    color #ffffff
 | 
			
		||||
    border-radius 10px;
 | 
			
		||||
 | 
			
		||||
    .logo {
 | 
			
		||||
      text-align center
 | 
			
		||||
 | 
			
		||||
      .el-image {
 | 
			
		||||
        width 120px;
 | 
			
		||||
        cursor pointer
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .header {
 | 
			
		||||
      width 100%
 | 
			
		||||
      margin-bottom 24px
 | 
			
		||||
      font-size 24px
 | 
			
		||||
      color $white_v1
 | 
			
		||||
      letter-space 2px
 | 
			
		||||
      text-align center
 | 
			
		||||
      padding-top 10px
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .content {
 | 
			
		||||
      width 100%
 | 
			
		||||
      height: auto
 | 
			
		||||
      border-radius 3px
 | 
			
		||||
 | 
			
		||||
      .block {
 | 
			
		||||
        margin-bottom 16px
 | 
			
		||||
 | 
			
		||||
        .el-input__inner {
 | 
			
		||||
          border 1px solid $gray-v6 !important
 | 
			
		||||
 | 
			
		||||
          .el-icon-user, .el-icon-lock {
 | 
			
		||||
            font-size 20px
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .btn-row {
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
 | 
			
		||||
        .login-btn {
 | 
			
		||||
          width 100%
 | 
			
		||||
          font-size 16px
 | 
			
		||||
          letter-spacing 2px
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .text-line {
 | 
			
		||||
        justify-content center
 | 
			
		||||
        padding-top 10px;
 | 
			
		||||
        font-size 14px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .opt {
 | 
			
		||||
        padding 15px
 | 
			
		||||
        .el-col {
 | 
			
		||||
          text-align center
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .wechat-login {
 | 
			
		||||
          color #0bc15f
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .footer {
 | 
			
		||||
    color #ffffff;
 | 
			
		||||
 | 
			
		||||
    .container {
 | 
			
		||||
      padding 20px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@import "@/assets/css/login.styl"
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										105
									
								
								web/src/views/LoginCallback.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								web/src/views/LoginCallback.vue
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,105 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <div class="login-callback"
 | 
			
		||||
       v-loading="loading"
 | 
			
		||||
       element-loading-text="正在同步登录信息..."
 | 
			
		||||
       :style="{ height: winHeight + 'px' }">
 | 
			
		||||
 | 
			
		||||
    <el-dialog
 | 
			
		||||
        v-model="show"
 | 
			
		||||
        :close-on-click-modal="false"
 | 
			
		||||
        :show-close="false"
 | 
			
		||||
        style="width: 360px;"
 | 
			
		||||
    >
 | 
			
		||||
      <el-result
 | 
			
		||||
          icon="success"
 | 
			
		||||
          title="登录成功"
 | 
			
		||||
          style="--el-result-padding:10px"
 | 
			
		||||
      >
 | 
			
		||||
          <template #sub-title>
 | 
			
		||||
            <div class="user-info">
 | 
			
		||||
              <div class="line">您的初始账户信息如下:</div>
 | 
			
		||||
              <div class="line"><span>用户名:</span>{{username}}</div>
 | 
			
		||||
              <div class="line"><span>密码:</span>{{password}}</div>
 | 
			
		||||
              <div class="line">您后期也可以通过此账号和密码登录</div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </template>
 | 
			
		||||
          <template #extra>
 | 
			
		||||
            <el-button type="primary" @click="finishLogin">我知道了</el-button>
 | 
			
		||||
          </template>
 | 
			
		||||
      </el-result>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup>
 | 
			
		||||
import {ref} from "vue"
 | 
			
		||||
import {useRouter} from "vue-router"
 | 
			
		||||
import {ElMessage, ElMessageBox} from "element-plus";
 | 
			
		||||
import {httpGet} from "@/utils/http";
 | 
			
		||||
import {setUserToken} from "@/store/session";
 | 
			
		||||
import {isMobile} from "@/utils/libs";
 | 
			
		||||
 | 
			
		||||
const winHeight = ref(window.innerHeight)
 | 
			
		||||
const loading = ref(true)
 | 
			
		||||
const router = useRouter()
 | 
			
		||||
const show = ref(false)
 | 
			
		||||
const username = ref('')
 | 
			
		||||
const password = ref('')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const code = router.currentRoute.value.query.code
 | 
			
		||||
if (code === "") {
 | 
			
		||||
  ElMessage.error({message: "登录失败:code 参数不能为空",duration: 2000, onClose: () => router.push("/")})
 | 
			
		||||
} else {
 | 
			
		||||
  // 发送请求获取用户信息
 | 
			
		||||
  httpGet("/api/user/clogin/callback",{login_type: "wx",code: code}).then(res => {
 | 
			
		||||
    setUserToken(res.data.token)
 | 
			
		||||
    if (res.data.username) {
 | 
			
		||||
      username.value = res.data.username
 | 
			
		||||
      password.value = res.data.password
 | 
			
		||||
      show.value = true
 | 
			
		||||
      loading.value = false
 | 
			
		||||
    } else {
 | 
			
		||||
      finishLogin()
 | 
			
		||||
    }
 | 
			
		||||
  }).catch(e => {
 | 
			
		||||
    ElMessageBox.alert(e.message, {
 | 
			
		||||
      confirmButtonText: '重新登录',
 | 
			
		||||
      type:"error",
 | 
			
		||||
      title:"登录失败",
 | 
			
		||||
      callback: () => {
 | 
			
		||||
        router.push("/login")
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
const finishLogin = () => {
 | 
			
		||||
  if (isMobile()) {
 | 
			
		||||
    router.push('/mobile')
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push('/chat')
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style lang="stylus" scoped>
 | 
			
		||||
.login-callback {
 | 
			
		||||
  .user-info {
 | 
			
		||||
    display flex
 | 
			
		||||
    flex-direction column
 | 
			
		||||
    padding 10px
 | 
			
		||||
    border 1px dashed #e1e1e1
 | 
			
		||||
    border-radius 10px
 | 
			
		||||
    .line {
 | 
			
		||||
      text-align left
 | 
			
		||||
      font-size 14px
 | 
			
		||||
      line-height 1.5
 | 
			
		||||
 | 
			
		||||
      span{
 | 
			
		||||
        font-weight bold
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
		Reference in New Issue
	
	Block a user