mirror of
https://github.com/yangjian102621/geekai.git
synced 2025-09-18 01:06:39 +08:00
wechat login is ready
This commit is contained in:
parent
b399fc557a
commit
bddd611cc1
@ -7,7 +7,7 @@
|
|||||||
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
|
* 功能新增:**支持AI解读 PDF, Word, Excel等文件**
|
||||||
* 功能优化:优化聊天界面的用户上传文件的列表样式
|
* 功能优化:优化聊天界面的用户上传文件的列表样式
|
||||||
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
|
* 功能优化:优化聊天页面对话样式,支持列表样式和对话样式切换
|
||||||
* 功能新增:支持微信等社交媒体登录
|
* 功能新增:支持微信扫码登录,未注册用户微信扫码后会自动注册并登录。移动使用微信浏览器打开可以实现无感登录。
|
||||||
|
|
||||||
|
|
||||||
## v4.0.9
|
## v4.0.9
|
||||||
|
@ -446,15 +446,10 @@ func (h *MidJourneyHandler) getData(finish bool, userId uint, page int, pageSize
|
|||||||
}
|
}
|
||||||
|
|
||||||
if item.Progress < 100 && item.ImgURL == "" && item.OrgURL != "" {
|
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)
|
image, err := utils.DownloadImage(item.OrgURL, h.App.Config.ProxyURL)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
job.ImgURL = "data:image/png;base64," + base64.StdEncoding.EncodeToString(image)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
job.ImgURL = job.OrgURL
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jobs = append(jobs, job)
|
jobs = append(jobs, job)
|
||||||
|
@ -285,9 +285,99 @@ func (h *UserHandler) CLoginRequest(c *gin.Context) {
|
|||||||
|
|
||||||
// CLoginCallback 第三方登录回调
|
// CLoginCallback 第三方登录回调
|
||||||
func (h *UserHandler) CLoginCallback(c *gin.Context) {
|
func (h *UserHandler) CLoginCallback(c *gin.Context) {
|
||||||
//platform := h.GetTrim(c, "type")
|
loginType := h.GetTrim(c, "login_type")
|
||||||
//code := h.GetTrim(c, "code")
|
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 获取/验证会话
|
// Session 获取/验证会话
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
ALTER TABLE `chatgpt_chat_models` CHANGE `power` `power` SMALLINT NOT NULL COMMENT '消耗算力点数';
|
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 `openid` VARCHAR(100) NULL COMMENT '第三方登录账号ID' AFTER `last_login_ip`;
|
||||||
|
ALTER TABLE `chatgpt_users` ADD UNIQUE(`openid`);
|
||||||
ALTER TABLE `chatgpt_users` ADD `platform` VARCHAR(30) NULL COMMENT '登录平台' AFTER `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) => {
|
const reGenerate = (prompt) => {
|
||||||
|
console.log(prompt)
|
||||||
emits('regen', prompt)
|
emits('regen', prompt)
|
||||||
}
|
}
|
||||||
</script>
|
</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: '用户登录'},
|
meta: {title: '用户登录'},
|
||||||
component: () => import('@/views/Login.vue'),
|
component: () => import('@/views/Login.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'login-callback',
|
||||||
|
path: '/login/callback',
|
||||||
|
meta: {title: '用户登录'},
|
||||||
|
component: () => import('@/views/LoginCallback.vue'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'register',
|
name: 'register',
|
||||||
path: '/register',
|
path: '/register',
|
||||||
|
@ -626,10 +626,12 @@ const connect = function (chat_id, role_id) {
|
|||||||
reader.onload = () => {
|
reader.onload = () => {
|
||||||
const data = JSON.parse(String(reader.result));
|
const data = JSON.parse(String(reader.result));
|
||||||
if (data.type === 'start') {
|
if (data.type === 'start') {
|
||||||
|
const prePrompt = chatData.value[chatData.value.length-1].content
|
||||||
chatData.value.push({
|
chatData.value.push({
|
||||||
type: "reply",
|
type: "reply",
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: _role['icon'],
|
icon: _role['icon'],
|
||||||
|
prompt:prePrompt,
|
||||||
content: ""
|
content: ""
|
||||||
});
|
});
|
||||||
} else if (data.type === 'end') { // 消息接收完毕
|
} else if (data.type === 'end') { // 消息接收完毕
|
||||||
@ -864,7 +866,7 @@ const reGenerate = function (prompt) {
|
|||||||
type: "prompt",
|
type: "prompt",
|
||||||
id: randString(32),
|
id: randString(32),
|
||||||
icon: loginUser.value.avatar,
|
icon: loginUser.value.avatar,
|
||||||
content: md.render(text)
|
content: text
|
||||||
});
|
});
|
||||||
socket.value.send(JSON.stringify({type: "chat", content: prompt}));
|
socket.value.send(JSON.stringify({type: "chat", content: prompt}));
|
||||||
}
|
}
|
||||||
|
@ -86,43 +86,7 @@
|
|||||||
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
||||||
<div class="job-list-box">
|
<div class="job-list-box">
|
||||||
<h2>任务列表</h2>
|
<h2>任务列表</h2>
|
||||||
<div class="running-job-list">
|
<task-list :list="runningJobs" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<h2>创作记录</h2>
|
<h2>创作记录</h2>
|
||||||
<div class="finish-job-list">
|
<div class="finish-job-list">
|
||||||
@ -228,6 +192,7 @@ import {ElMessage, ElMessageBox, ElNotification} from "element-plus";
|
|||||||
import Clipboard from "clipboard";
|
import Clipboard from "clipboard";
|
||||||
import {checkSession} from "@/action/session";
|
import {checkSession} from "@/action/session";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
|
import TaskList from "@/components/TaskList.vue";
|
||||||
|
|
||||||
const listBoxHeight = ref(0)
|
const listBoxHeight = ref(0)
|
||||||
// const paramBoxHeight = ref(0)
|
// const paramBoxHeight = ref(0)
|
||||||
|
@ -215,10 +215,11 @@ const init = () => {
|
|||||||
const logout = function () {
|
const logout = function () {
|
||||||
httpGet('/api/user/logout').then(() => {
|
httpGet('/api/user/logout').then(() => {
|
||||||
removeUserToken()
|
removeUserToken()
|
||||||
store.setShowLoginDialog(true)
|
router.push("/login")
|
||||||
loginUser.value = {}
|
// store.setShowLoginDialog(true)
|
||||||
// 刷新组件
|
// loginUser.value = {}
|
||||||
routerViewKey.value += 1
|
// // 刷新组件
|
||||||
|
// routerViewKey.value += 1
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
ElMessage.error('注销失败!');
|
ElMessage.error('注销失败!');
|
||||||
})
|
})
|
||||||
|
@ -163,7 +163,7 @@
|
|||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
</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="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
||||||
<div class="extra-params">
|
<div class="extra-params">
|
||||||
<el-form>
|
<el-form>
|
||||||
@ -450,43 +450,7 @@
|
|||||||
|
|
||||||
<div class="job-list-box">
|
<div class="job-list-box">
|
||||||
<h2>任务列表</h2>
|
<h2>任务列表</h2>
|
||||||
<div class="running-job-list">
|
<task-list :list="runningJobs" />
|
||||||
<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>
|
|
||||||
|
|
||||||
<h2>创作记录</h2>
|
<h2>创作记录</h2>
|
||||||
<div class="finish-job-list">
|
<div class="finish-job-list">
|
||||||
@ -617,6 +581,7 @@ import {useRouter} from "vue-router";
|
|||||||
import {getSessionId} from "@/store/session";
|
import {getSessionId} from "@/store/session";
|
||||||
import {copyObj, removeArrayItem} from "@/utils/libs";
|
import {copyObj, removeArrayItem} from "@/utils/libs";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
|
import TaskList from "@/components/TaskList.vue";
|
||||||
|
|
||||||
const listBoxHeight = ref(0)
|
const listBoxHeight = ref(0)
|
||||||
const paramBoxHeight = ref(0)
|
const paramBoxHeight = ref(0)
|
||||||
|
@ -296,39 +296,8 @@
|
|||||||
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
<div class="task-list-inner" :style="{ height: listBoxHeight + 'px' }">
|
||||||
<div class="job-list-box">
|
<div class="job-list-box">
|
||||||
<h2>任务列表</h2>
|
<h2>任务列表</h2>
|
||||||
<div class="running-job-list">
|
<task-list :list="runningJobs" />
|
||||||
<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>
|
|
||||||
<h2>创作记录</h2>
|
<h2>创作记录</h2>
|
||||||
<div class="finish-job-list">
|
<div class="finish-job-list">
|
||||||
<div v-if="finishedJobs.length > 0">
|
<div v-if="finishedJobs.length > 0">
|
||||||
@ -506,6 +475,7 @@ import {checkSession} from "@/action/session";
|
|||||||
import {useRouter} from "vue-router";
|
import {useRouter} from "vue-router";
|
||||||
import {getSessionId} from "@/store/session";
|
import {getSessionId} from "@/store/session";
|
||||||
import {useSharedStore} from "@/store/sharedata";
|
import {useSharedStore} from "@/store/sharedata";
|
||||||
|
import TaskList from "@/components/TaskList.vue";
|
||||||
|
|
||||||
const listBoxHeight = ref(0)
|
const listBoxHeight = ref(0)
|
||||||
// const paramBoxHeight = ref(0)
|
// const paramBoxHeight = ref(0)
|
||||||
|
@ -35,19 +35,22 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-row class="opt" :gutter="24">
|
<el-row class="opt" :gutter="24">
|
||||||
<el-col :span="span" v-if="cLoginURL !== ''">
|
<el-col :span="8"><el-link type="primary" @click="router.push('/register')">注册</el-link></el-col>
|
||||||
<el-tooltip class="item" effect="light" content="微信扫码登录" placement="top">
|
<el-col :span="8">
|
||||||
<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-link type="info" @click="showResetPass = true">重置密码</el-link>
|
<el-link type="info" @click="showResetPass = true">重置密码</el-link>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="span">
|
<el-col :span="8">
|
||||||
<el-link type="info" @click="router.push('/')">首页</el-link>
|
<el-link type="info" @click="router.push('/')">首页</el-link>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -80,8 +83,7 @@ const password = ref(process.env.VUE_APP_PASS);
|
|||||||
const showResetPass = ref(false)
|
const showResetPass = ref(false)
|
||||||
const logo = ref("/images/logo.png")
|
const logo = ref("/images/logo.png")
|
||||||
const licenseConfig = ref({})
|
const licenseConfig = ref({})
|
||||||
const cLoginURL = ref('')
|
const wechatLoginURL = ref('')
|
||||||
const span = ref(8)
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 获取系统配置
|
// 获取系统配置
|
||||||
@ -94,7 +96,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
httpGet("/api/config/license").then(res => {
|
httpGet("/api/config/license").then(res => {
|
||||||
licenseConfig.value = res.data
|
licenseConfig.value = res.data
|
||||||
span.value = 6
|
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
showMessageError("获取 License 配置:" + e.message)
|
showMessageError("获取 License 配置:" + e.message)
|
||||||
})
|
})
|
||||||
@ -108,10 +109,9 @@ onMounted(() => {
|
|||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// const returnURL = `${location.protocol}//${location.host}/user/api/clogin/callback`
|
const returnURL = `${location.protocol}//${location.host}/login/callback`
|
||||||
const returnURL = `https://ai.r9it.com/user/api/clogin/callback`
|
|
||||||
httpGet("/api/user/clogin/request?return_url="+returnURL).then(res => {
|
httpGet("/api/user/clogin/request?return_url="+returnURL).then(res => {
|
||||||
cLoginURL.value = res.data.url
|
wechatLoginURL.value = res.data.url
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
})
|
})
|
||||||
@ -147,103 +147,5 @@ const login = function () {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
.bg {
|
@import "@/assets/css/login.styl"
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</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>
|
Loading…
Reference in New Issue
Block a user