增加移动端登录页面

This commit is contained in:
RockYang 2024-12-26 16:52:20 +08:00
parent acee2d9d81
commit 8af0fec8ec
25 changed files with 831 additions and 1012 deletions

View File

@ -9,7 +9,8 @@
- Bug 修复:修复 Suno 已完成任务删除失败的 错误
- 功能新增:支持 OpenAI 实时语音通话功能,目前已经支持按次收费,支持管理员设置每次实时语音通话的算力消耗
- 功能新增:生成提示词需要消耗算力,支持管理员设置每次生成提示词的算力消耗,防止被白嫖
- 功能新增DALL-E-3 绘图支持 Gitee AI Flex 接口
- 功能新增DALL-E-3 绘图支持 Flux 绘图模型,支持在管理后添加 Flux,SD 等绘图模型
- 功能优化Markdown 支持解析 emoji 表情
## v4.1.7

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 337 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View File

@ -2,6 +2,7 @@
--sm-txt:rgba(163, 174, 208, 1);
--text-secondary: #8a939d;
--el-color-primary: rgb(107, 80, 225);
--van-primary-color:rgb(107, 80, 225);
--theme-textcolor-normal:#b0a0f8;
--el-border-radius-base: 5px;
--el-color-primary-light-5:rgb(107, 85, 255);

View File

@ -20,8 +20,12 @@
height: 100vh;
.title{
font-size: 28px
height: 40px
width 120px
text-align: center;
word-wrap break-all;
overflow hidden
font-weight: 700
margin-right: 6px
color:var(--text-theme-color)
}
img{

View File

@ -52,6 +52,8 @@
.list-item {
display flex
.image {
overflow hidden

View File

@ -70,8 +70,10 @@
}
.logo {
height 50px
height 60px
width 60px
border-radius 50%
background: var(--theme-bg)
}
.el-button {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 75 KiB

View File

@ -1,130 +1,138 @@
<template>
<el-container class="captcha-box">
<el-dialog
v-model="show"
:close-on-click-modal="true"
:show-close="false"
style="width: 360px;"
>
<el-dialog v-model="show" :close-on-click-modal="true" :show-close="isMobileInternal" style="width: 360px; --el-dialog-padding-primary: 5px 15px 15px 15px">
<template #title>
<div class="text-center p-3" style="color: var(--el-text-color-primary)" v-if="isMobileInternal">
<span>人机验证</span>
</div>
</template>
<slide-captcha
v-if="isMobile()"
:bg-img="bgImg"
:bk-img="bkImg"
:result="result"
@refresh="getSlideCaptcha"
@confirm="handleSlideConfirm"
@hide="show = false"/>
v-if="isMobileInternal"
:bg-img="bgImg"
:bk-img="bkImg"
:result="result"
@refresh="getSlideCaptcha"
@confirm="handleSlideConfirm"
@hide="show = false"
/>
<captcha-plus
v-else
:max-dot="maxDot"
:image-base64="imageBase64"
:thumb-base64="thumbBase64"
width="300"
@close="show = false"
@refresh="handleRequestCaptCode"
@confirm="handleConfirm"
v-else
:max-dot="maxDot"
:image-base64="imageBase64"
:thumb-base64="thumbBase64"
width="300"
@close="show = false"
@refresh="handleRequestCaptCode"
@confirm="handleConfirm"
/>
</el-dialog>
</el-container>
</template>
<script setup>
import {ref} from "vue";
import lodash from 'lodash'
import {validateEmail, validateMobile} from "@/utils/validate";
import {httpGet, httpPost} from "@/utils/http";
import { ref } from "vue";
import lodash from "lodash";
import { validateEmail, validateMobile } from "@/utils/validate";
import { httpGet, httpPost } from "@/utils/http";
import CaptchaPlus from "@/components/CaptchaPlus.vue";
import SlideCaptcha from "@/components/SlideCaptcha.vue";
import {isMobile} from "@/utils/libs";
import {showMessageError, showMessageOK} from "@/utils/dialog";
import { isMobile } from "@/utils/libs";
import { showMessageError, showMessageOK } from "@/utils/dialog";
const show = ref(false)
const maxDot = ref(5)
const imageBase64 = ref('')
const thumbBase64 = ref('')
const captKey = ref('')
const dots = ref(null)
const show = ref(false);
const maxDot = ref(5);
const imageBase64 = ref("");
const thumbBase64 = ref("");
const captKey = ref("");
const dots = ref(null);
const isMobileInternal = isMobile();
const emits = defineEmits(['success']);
const emits = defineEmits(["success"]);
const handleRequestCaptCode = () => {
httpGet('/api/captcha/get').then(res => {
const data = res.data
imageBase64.value = data.image
thumbBase64.value = data.thumb
captKey.value = data.key
}).catch(e => {
showMessageError('获取人机验证数据失败:' + e.message)
})
}
httpGet("/api/captcha/get")
.then((res) => {
const data = res.data;
imageBase64.value = data.image;
thumbBase64.value = data.thumb;
captKey.value = data.key;
})
.catch((e) => {
showMessageError("获取人机验证数据失败:" + e.message);
});
};
const handleConfirm = (dts) => {
if (lodash.size(dts) <= 0) {
return showMessageError('请进行人机验证再操作')
return showMessageError("请进行人机验证再操作");
}
let dotArr = []
let dotArr = [];
lodash.forEach(dts, (dot) => {
dotArr.push(dot.x, dot.y)
})
dots.value = dotArr.join(',')
httpPost('/api/captcha/check', {
dotArr.push(dot.x, dot.y);
});
dots.value = dotArr.join(",");
httpPost("/api/captcha/check", {
dots: dots.value,
key: captKey.value
}).then(() => {
// ElMessage.success('')
show.value = false
emits('success', {key:captKey.value, dots:dots.value})
}).catch(() => {
showMessageError('人机验证失败')
handleRequestCaptCode()
key: captKey.value,
})
}
.then(() => {
// ElMessage.success('')
show.value = false;
emits("success", { key: captKey.value, dots: dots.value });
})
.catch(() => {
showMessageError("人机验证失败");
handleRequestCaptCode();
});
};
const loadCaptcha = () => {
show.value = true
show.value = true;
//
if (isMobile()) {
getSlideCaptcha()
getSlideCaptcha();
} else {
handleRequestCaptCode()
handleRequestCaptCode();
}
}
};
//
const bgImg = ref('')
const bkImg = ref('')
const result = ref(0)
const bgImg = ref("");
const bkImg = ref("");
const result = ref(0);
const getSlideCaptcha = () => {
result.value = 0
httpGet("/api/captcha/slide/get").then(res => {
bkImg.value = res.data.bkImg
bgImg.value = res.data.bgImg
captKey.value = res.data.key
}).catch(e => {
showMessageError('获取人机验证数据失败:' + e.message)
})
}
result.value = 0;
httpGet("/api/captcha/slide/get")
.then((res) => {
bkImg.value = res.data.bkImg;
bgImg.value = res.data.bgImg;
captKey.value = res.data.key;
})
.catch((e) => {
showMessageError("获取人机验证数据失败:" + e.message);
});
};
const handleSlideConfirm = (x) => {
httpPost("/api/captcha/slide/check", {
key: captKey.value,
x: x
}).then(() => {
result.value = 1
show.value = false
emits('success',{key:captKey.value, x:x})
}).catch(() => {
result.value = 2
x: x,
})
}
.then(() => {
result.value = 1;
show.value = false;
emits("success", { key: captKey.value, x: x });
})
.catch(() => {
result.value = 2;
});
};
// 便
defineExpose({
loadCaptcha
loadCaptcha,
});
</script>
@ -137,8 +145,9 @@ defineExpose({
}
.el-dialog__body {
padding 0
padding-bottom: 5px;
overflow: hidden;
}
}
}
</style>
</style>

View File

@ -1,90 +1,93 @@
<template>
<div class="wg-cap-wrap" :style="{width: width}">
<div class="wg-cap-wrap" :style="{ width: width }">
<div class="wg-cap-wrap__header">
<span>请在下图<em>依次</em>点击</span>
<img class="wg-cap-wrap__thumb" v-if="thumbBase64Code" :src="thumbBase64Code" alt=" ">
<img class="wg-cap-wrap__thumb" v-if="thumbBase64Code" :src="thumbBase64Code" alt=" " />
</div>
<div class="wg-cap-wrap__body">
<img class="wg-cap-wrap__picture" v-if="imageBase64Code" :src="imageBase64Code" alt=" "
@click="handleClickPos($event)">
<img class="wg-cap-wrap__loading"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2JhKDI0MSwgMjQyLCAyNDMsIDApOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjM2LjgxMDEiIHI9IjEzIiBmaWxsPSIjM2U3Y2ZmIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN5IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgY2FsY01vZGU9InNwbGluZSIga2V5U3BsaW5lcz0iMC40NSAwIDAuOSAwLjU1OzAgMC40NSAwLjU1IDAuOSIga2V5VGltZXM9IjA7MC41OzEiIHZhbHVlcz0iMjM7Nzc7MjMiPjwvYW5pbWF0ZT4KICA8L2NpcmNsZT4KPC9zdmc+"
alt="正在加载中...">
<img class="wg-cap-wrap__picture" v-if="imageBase64Code" :src="imageBase64Code" alt=" " @click="handleClickPos($event)" />
<img
class="wg-cap-wrap__loading"
src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiByZ2JhKDI0MSwgMjQyLCAyNDMsIDApOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyIgd2lkdGg9IjY0cHgiIGhlaWdodD0iNjRweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KICA8Y2lyY2xlIGN4PSI1MCIgY3k9IjM2LjgxMDEiIHI9IjEzIiBmaWxsPSIjM2U3Y2ZmIj4KICAgIDxhbmltYXRlIGF0dHJpYnV0ZU5hbWU9ImN5IiBkdXI9IjFzIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgY2FsY01vZGU9InNwbGluZSIga2V5U3BsaW5lcz0iMC40NSAwIDAuOSAwLjU1OzAgMC40NSAwLjU1IDAuOSIga2V5VGltZXM9IjA7MC41OzEiIHZhbHVlcz0iMjM7Nzc7MjMiPjwvYW5pbWF0ZT4KICA8L2NpcmNsZT4KPC9zdmc+"
alt="正在加载中..."
/>
<div v-for="(dot, key) in dots" :key="key" class="wg-cap-wrap__dot" :style="`top: ${dot.y}px; left:${dot.x}px;`">
<span>{{ dot.index }}</span>
</div>
</div>
<div class="wg-cap-wrap__footer">
<div class="wg-cap-wrap__ico">
<img @click="handleCloseEvent"
src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDM5NDIzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijg2NzUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDIzLjI3MjcyN2MyNjkuOTE3MDkxIDAgNDg4LjcyNzI3MyAyMTguODEwMTgyIDQ4OC43MjcyNzMgNDg4LjcyNzI3M2E0ODYuNjMyNzI3IDQ4Ni42MzI3MjcgMCAwIDEtODQuOTQ1NDU1IDI3NS40MDk0NTUgNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDAgMS03Ni44NDY1NDUtNTIuNTI2NTQ2QTM5My41NDE4MTggMzkzLjU0MTgxOCAwIDAgMCA5MDcuNjM2MzY0IDUxMmMwLTIxOC41MDc2MzYtMTc3LjEyODcyNy0zOTUuNjM2MzY0LTM5NS42MzYzNjQtMzk1LjYzNjM2NFMxMTYuMzYzNjM2IDI5My40OTIzNjQgMTE2LjM2MzYzNiA1MTJzMTc3LjEyODcyNyAzOTUuNjM2MzY0IDM5NS42MzYzNjQgMzk1LjYzNjM2NGEzOTUuMTcwOTA5IDM5NS4xNzA5MDkgMCAwIDAgMTI1LjQ0LTIwLjI5MzgxOSA0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMCAxIDI5LjQ4NjU0NSA4OC4yOTY3MjhBNDg4LjI2MTgxOCA0ODguMjYxODE4IDAgMCAxIDUxMiAxMDAwLjcyNzI3M0MyNDIuMDgyOTA5IDEwMDAuNzI3MjczIDIzLjI3MjcyNyA3ODEuOTE3MDkxIDIzLjI3MjcyNyA1MTJTMjQyLjA4MjkwOSAyMy4yNzI3MjcgNTEyIDIzLjI3MjcyN3ogbS0xMTUuMiAzMDcuNzEyTDUxMiA0NDYuMTM4MTgybDExNS4yLTExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEgNjUuODE1MjczIDY1Ljg2MTgxOEw1NzcuODYxODE4IDUxMmwxMTUuMiAxMTUuMmE0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMSAxLTY1Ljg2MTgxOCA2NS44MTUyNzNMNTEyIDU3Ny44NjE4MThsLTExNS4yIDExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEtNjUuODE1MjczLTY1Ljg2MTgxOEw0NDYuMTM4MTgyIDUxMmwtMTE1LjItMTE1LjJhNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDEgMSA2NS44NjE4MTgtNjUuODE1MjczeiIgcC1pZD0iODY3NiIgZmlsbD0iIzcwNzA3MCI+PC9wYXRoPjwvc3ZnPg=="
alt="关闭">
<img @click="handleRefreshEvent"
src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDk5NjM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEzNjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTg3LjQ1NiA0MjUuMDI0YTMzNiAzMzYgMCAwIDAgMzY4LjM4NCA0MjAuMjI0IDQ4IDQ4IDAgMCAxIDEyLjU0NCA5NS4xNjggNDMyIDQzMiAwIDAgMS00NzMuNjY0LTU0MC4xNmwtNTcuMjgtMTUuMzZhMTIuOCAxMi44IDAgMCAxLTYuMjcyLTIwLjkyOGwxNTkuMTY4LTE3OS40NTZhMTIuOCAxMi44IDAgMCAxIDIyLjE0NCA1Ljg4OGw0OC4wNjQgMjM1LjA3MmExMi44IDEyLjggMCAwIDEtMTUuODA4IDE0LjkxMmwtNTcuMjgtMTUuMzZ6TTgzNi40OCA1OTkuMDRhMzM2IDMzNiAwIDAgMC0zNjguMzg0LTQyMC4yMjQgNDggNDggMCAxIDEtMTIuNTQ0LTk1LjE2OCA0MzIgNDMyIDAgMCAxIDQ3My42NjQgNTQwLjE2bDU3LjI4IDE1LjM2YTEyLjggMTIuOCAwIDAgMSA2LjI3MiAyMC45MjhsLTE1OS4xNjggMTc5LjQ1NmExMi44IDEyLjggMCAwIDEtMjIuMTQ0LTUuODg4bC00OC4wNjQtMjM1LjA3MmExMi44IDEyLjggMCAwIDEgMTUuODA4LTE0LjkxMmw1Ny4yOCAxNS4zNnoiIGZpbGw9IiM3MDcwNzAiIHAtaWQ9IjEzNjEiPjwvcGF0aD48L3N2Zz4="
alt="刷新">
<div class="wg-cap-wrap__ico flex">
<img
@click="handleCloseEvent"
src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDM5NDIzIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9Ijg2NzUiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNNTEyIDIzLjI3MjcyN2MyNjkuOTE3MDkxIDAgNDg4LjcyNzI3MyAyMTguODEwMTgyIDQ4OC43MjcyNzMgNDg4LjcyNzI3M2E0ODYuNjMyNzI3IDQ4Ni42MzI3MjcgMCAwIDEtODQuOTQ1NDU1IDI3NS40MDk0NTUgNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDAgMS03Ni44NDY1NDUtNTIuNTI2NTQ2QTM5My41NDE4MTggMzkzLjU0MTgxOCAwIDAgMCA5MDcuNjM2MzY0IDUxMmMwLTIxOC41MDc2MzYtMTc3LjEyODcyNy0zOTUuNjM2MzY0LTM5NS42MzYzNjQtMzk1LjYzNjM2NFMxMTYuMzYzNjM2IDI5My40OTIzNjQgMTE2LjM2MzYzNiA1MTJzMTc3LjEyODcyNyAzOTUuNjM2MzY0IDM5NS42MzYzNjQgMzk1LjYzNjM2NGEzOTUuMTcwOTA5IDM5NS4xNzA5MDkgMCAwIDAgMTI1LjQ0LTIwLjI5MzgxOSA0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMCAxIDI5LjQ4NjU0NSA4OC4yOTY3MjhBNDg4LjI2MTgxOCA0ODguMjYxODE4IDAgMCAxIDUxMiAxMDAwLjcyNzI3M0MyNDIuMDgyOTA5IDEwMDAuNzI3MjczIDIzLjI3MjcyNyA3ODEuOTE3MDkxIDIzLjI3MjcyNyA1MTJTMjQyLjA4MjkwOSAyMy4yNzI3MjcgNTEyIDIzLjI3MjcyN3ogbS0xMTUuMiAzMDcuNzEyTDUxMiA0NDYuMTM4MTgybDExNS4yLTExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEgNjUuODE1MjczIDY1Ljg2MTgxOEw1NzcuODYxODE4IDUxMmwxMTUuMiAxMTUuMmE0Ni41NDU0NTUgNDYuNTQ1NDU1IDAgMSAxLTY1Ljg2MTgxOCA2NS44MTUyNzNMNTEyIDU3Ny44NjE4MThsLTExNS4yIDExNS4yYTQ2LjU0NTQ1NSA0Ni41NDU0NTUgMCAxIDEtNjUuODE1MjczLTY1Ljg2MTgxOEw0NDYuMTM4MTgyIDUxMmwtMTE1LjItMTE1LjJhNDYuNTQ1NDU1IDQ2LjU0NTQ1NSAwIDEgMSA2NS44NjE4MTgtNjUuODE1MjczeiIgcC1pZD0iODY3NiIgZmlsbD0iIzcwNzA3MCI+PC9wYXRoPjwvc3ZnPg=="
alt="关闭"
/>
<img
@click="handleRefreshEvent"
src="data:image/svg+xml;base64,PHN2ZyB0PSIxNjI2NjE0NDk5NjM4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjEzNjAiIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIj48cGF0aCBkPSJNMTg3LjQ1NiA0MjUuMDI0YTMzNiAzMzYgMCAwIDAgMzY4LjM4NCA0MjAuMjI0IDQ4IDQ4IDAgMCAxIDEyLjU0NCA5NS4xNjggNDMyIDQzMiAwIDAgMS00NzMuNjY0LTU0MC4xNmwtNTcuMjgtMTUuMzZhMTIuOCAxMi44IDAgMCAxLTYuMjcyLTIwLjkyOGwxNTkuMTY4LTE3OS40NTZhMTIuOCAxMi44IDAgMCAxIDIyLjE0NCA1Ljg4OGw0OC4wNjQgMjM1LjA3MmExMi44IDEyLjggMCAwIDEtMTUuODA4IDE0LjkxMmwtNTcuMjgtMTUuMzZ6TTgzNi40OCA1OTkuMDRhMzM2IDMzNiAwIDAgMC0zNjguMzg0LTQyMC4yMjQgNDggNDggMCAxIDEtMTIuNTQ0LTk1LjE2OCA0MzIgNDMyIDAgMCAxIDQ3My42NjQgNTQwLjE2bDU3LjI4IDE1LjM2YTEyLjggMTIuOCAwIDAgMSA2LjI3MiAyMC45MjhsLTE1OS4xNjggMTc5LjQ1NmExMi44IDEyLjggMCAwIDEtMjIuMTQ0LTUuODg4bC00OC4wNjQtMjM1LjA3MmExMi44IDEyLjggMCAwIDEgMTUuODA4LTE0LjkxMmw1Ny4yOCAxNS4zNnoiIGZpbGw9IiM3MDcwNzAiIHAtaWQ9IjEzNjEiPjwvcGF0aD48L3N2Zz4="
alt="刷新"
/>
</div>
<div class="wg-cap-wrap__btn">
<el-button type="primary" @click="handleConfirmEvent">确认</el-button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'CaptchaPlus',
name: "CaptchaPlus",
mounted() {
this.$emit('refresh')
this.$emit("refresh");
},
props: {
value: Boolean,
width: {
type: String,
default: '300px'
default: "300px",
},
calcPosType: {
type: String,
default: 'dom',
validator: value => ['dom', 'screen'].includes(value)
default: "dom",
validator: (value) => ["dom", "screen"].includes(value),
},
maxDot: {
type: Number,
default: 5
default: 5,
// validator: value => value > 10
},
imageBase64: String,
thumbBase64: String
thumbBase64: String,
},
data() {
return {
dots: [],
imageBase64Code: '',
thumbBase64Code: ''
}
imageBase64Code: "",
thumbBase64Code: "",
};
},
watch: {
value() {
this.dots = []
this.imageBase64Code = ''
this.thumbBase64Code = ''
this.dots = [];
this.imageBase64Code = "";
this.thumbBase64Code = "";
},
imageBase64(val) {
this.dots = []
this.imageBase64Code = val
this.dots = [];
this.imageBase64Code = val;
},
thumbBase64(val) {
this.dots = []
this.thumbBase64Code = val
}
this.dots = [];
this.thumbBase64Code = val;
},
},
methods: {
/**
* @Description: 处理关闭事件
*/
handleCloseEvent() {
this.$emit('close')
this.$emit("close");
// this.dots = []
// this.imageBase64Code = ''
// this.thumbBase64Code = ''
@ -93,14 +96,14 @@ export default {
* @Description: 处理刷新事件
*/
handleRefreshEvent() {
this.dots = []
this.$emit('refresh')
this.dots = [];
this.$emit("refresh");
},
/**
* @Description: 处理确认事件
*/
handleConfirmEvent() {
this.$emit('confirm', this.dots)
this.$emit("confirm", this.dots);
},
/**
* @Description: 处理dot
@ -108,102 +111,102 @@ export default {
*/
handleClickPos(ev) {
if (this.dots.length >= this.maxDot) {
return
return;
}
const e = ev || window.event
e.preventDefault()
const dom = e.currentTarget
const e = ev || window.event;
e.preventDefault();
const dom = e.currentTarget;
const {domX, domY} = this.getDomXY(dom)
const { domX, domY } = this.getDomXY(dom);
// ===============================================
// @notice getDomXY 使 calcLocationLeft calcLocationTop
// const domX = this.calcLocationLeft(dom)
// const domY = this.calcLocationTop(dom)
// ===============================================
let mouseX = (navigator.vendor === 'Netscape') ? e.pageX : e.x + document.body.offsetTop
let mouseY = (navigator.vendor === 'Netscape') ? e.pageY : e.y + document.body.offsetTop
let mouseX = navigator.vendor === "Netscape" ? e.pageX : e.x + document.body.offsetTop;
let mouseY = navigator.vendor === "Netscape" ? e.pageY : e.y + document.body.offsetTop;
//
if (e.touches && e.touches.length > 0) {
mouseX = e.touches[0].clientX
mouseY = e.touches[0].clientY
mouseX = e.touches[0].clientX;
mouseY = e.touches[0].clientY;
} else {
mouseX = e.clientX
mouseY = e.clientY
mouseX = e.clientX;
mouseY = e.clientY;
}
//
const xPos = mouseX - domX
const yPos = mouseY - domY
const xPos = mouseX - domX;
const yPos = mouseY - domY;
//
const xp = parseInt(xPos.toString())
const yp = parseInt(yPos.toString())
const xp = parseInt(xPos.toString());
const yp = parseInt(yPos.toString());
//
this.dots.push({
x: xp - 11,
y: yp - 11,
index: this.dots.length + 1
})
return false
index: this.dots.length + 1,
});
return false;
},
/**
* @Description: 找到元素的屏幕位置
* @param el
*/
calcLocationLeft(el) {
let tmp = el.offsetLeft
let val = el.offsetParent
let tmp = el.offsetLeft;
let val = el.offsetParent;
while (val != null) {
tmp += val.offsetLeft
val = val.offsetParent
tmp += val.offsetLeft;
val = val.offsetParent;
}
return tmp
return tmp;
},
/**
* @Description: 找到元素的屏幕位置
* @param el
*/
calcLocationTop(el) {
let tmp = el.offsetTop
let val = el.offsetParent
let tmp = el.offsetTop;
let val = el.offsetParent;
while (val != null) {
tmp += val.offsetTop
val = val.offsetParent
tmp += val.offsetTop;
val = val.offsetParent;
}
return tmp
return tmp;
},
/**
* @Description: 找到元素的屏幕位置
* @param dom
*/
getDomXY(dom) {
let x = 0
let y = 0
let x = 0;
let y = 0;
if (dom.getBoundingClientRect) {
let box = dom.getBoundingClientRect();
let D = document.documentElement;
x = box.left + Math.max(D.scrollLeft, document.body.scrollLeft) - D.clientLeft;
y = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop
y = box.top + Math.max(D.scrollTop, document.body.scrollTop) - D.clientTop;
} else {
while (dom !== document.body) {
x += dom.offsetLeft
y += dom.offsetTop
dom = dom.offsetParent
x += dom.offsetLeft;
y += dom.offsetTop;
dom = dom.offsetParent;
}
}
return {
domX: x,
domY: y
}
}
}
}
domY: y,
};
},
},
};
</script>
<style scoped lang="stylus">
.wg-cap-wrap {
background: #ffffff;
background: var(--el-bg-color);
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
@ -219,14 +222,7 @@ export default {
height: 50px;
width: 100%;
font-size: 15px;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
span {

View File

@ -1,32 +1,9 @@
<template>
<el-dialog
class="login-dialog"
v-model="showDialog"
:close-on-click-modal="true"
:show-close="false"
:before-close="close"
>
<template #header="{ titleId, titleClass }">
<div class="header">
<div class="title" v-if="login">用户登录-</div>
<div class="title" v-else>用户注册</div>
<div class="close-icon">
<el-icon @click="close">
<Close />
</el-icon>
</div>
</div>
</template>
<div class="login-dialog w-full p-8">
<div class="login-box" v-if="login">
<el-form :model="data" label-width="120px" class="form">
<el-form :model="data" class="form">
<div class="block">
<el-input
placeholder="账号"
size="large"
v-model="data.username"
autocomplete="off"
>
<el-input placeholder="账号" size="large" v-model="data.username" autocomplete="off">
<template #prefix>
<el-icon>
<Iphone />
@ -36,14 +13,7 @@
</div>
<div class="block">
<el-input
placeholder="请输入密码(8-16位)"
maxlength="16"
size="large"
v-model="data.password"
show-password
autocomplete="off"
>
<el-input placeholder="请输入密码(8-16位)" maxlength="16" size="large" v-model="data.password" show-password autocomplete="off">
<template #prefix>
<el-icon>
<Lock />
@ -54,67 +24,42 @@
<el-row class="btn-row" :gutter="20">
<el-col :span="24">
<el-button
class="login-btn"
type="primary"
size="large"
@click="submitLogin"
>登录</el-button
>
<el-button class="login-btn" type="primary" size="large" @click="submitLogin"> </el-button>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<div class="reg">
还没有账号
<el-button
type="primary"
class="forget"
size="small"
@click="login = false"
>注册</el-button
>
<div class="w-full">
<div class="text flex justify-center items-center pt-3 text-sm">
还没有账号
<el-button size="small" @click="login = false">注册</el-button>
<el-button
type="info"
class="forget"
size="small"
@click="showResetPass = true"
>忘记密码</el-button
>
</div>
</el-col>
<el-col :span="12">
<div class="c-login" v-if="wechatLoginURL !== ''">
<div class="text">其他登录方式</div>
<div class="login-type">
<a
class="wechat-login"
:href="wechatLoginURL"
@click="setRoute(router.currentRoute.value.path)"
><i class="iconfont icon-wechat"></i
></a>
<el-button type="info" class="forget" size="small" @click="showResetPass = true">忘记密码</el-button>
</div>
<div v-if="wechatLoginURL !== ''">
<el-divider>
<div class="text-center">其他登录方式</div>
</el-divider>
<div class="c-login flex justify-center">
<!-- <div class="login-type mr-2">
<a class="wechat-login" :href="wechatLoginURL" @click="setRoute(router.currentRoute.value.path)"><i class="iconfont icon-wechat"></i></a>
</div> -->
<div class="p-2 w-full">
<el-button type="success" class="w-full" size="large" :href="wechatLoginURL" @click="setRoute(router.currentRoute.value.path)"
><i class="iconfont icon-wechat mr-2"></i> 微信登录
</el-button>
</div>
</div>
</el-col>
</el-row>
</div>
</div>
</el-form>
</div>
<div class="register-box" v-else>
<div class="register-box w-full" v-else>
<el-form :model="data" class="form" v-if="enableRegister">
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="手机注册" name="mobile" v-if="enableMobile">
<div class="block">
<el-input
placeholder="手机号码"
size="large"
v-model="data.mobile"
maxlength="11"
autocomplete="off"
>
<el-input placeholder="手机号码" size="large" v-model="data.mobile" maxlength="11" autocomplete="off">
<template #prefix>
<el-icon>
<Iphone />
@ -125,13 +70,7 @@
<div class="block">
<el-row :gutter="10">
<el-col :span="12">
<el-input
placeholder="验证码"
size="large"
maxlength="30"
v-model="data.code"
autocomplete="off"
>
<el-input placeholder="验证码" size="large" maxlength="30" v-model="data.code" autocomplete="off">
<template #prefix>
<el-icon>
<Checked />
@ -140,23 +79,14 @@
</el-input>
</el-col>
<el-col :span="12">
<send-msg
size="large"
:receiver="data.mobile"
type="mobile"
/>
<send-msg size="large" :receiver="data.mobile" type="mobile" />
</el-col>
</el-row>
</div>
</el-tab-pane>
<el-tab-pane label="邮箱注册" name="email" v-if="enableEmail">
<div class="block">
<el-input
placeholder="邮箱地址"
size="large"
v-model="data.email"
autocomplete="off"
>
<el-input placeholder="邮箱地址" size="large" v-model="data.email" autocomplete="off">
<template #prefix>
<el-icon>
<Message />
@ -167,13 +97,7 @@
<div class="block">
<el-row :gutter="10">
<el-col :span="12">
<el-input
placeholder="验证码"
size="large"
maxlength="30"
v-model="data.code"
autocomplete="off"
>
<el-input placeholder="验证码" size="large" maxlength="30" v-model="data.code" autocomplete="off">
<template #prefix>
<el-icon>
<Checked />
@ -189,12 +113,7 @@
</el-tab-pane>
<el-tab-pane label="用户名注册" name="username" v-if="enableUser">
<div class="block">
<el-input
placeholder="用户名"
size="large"
v-model="data.username"
autocomplete="off"
>
<el-input placeholder="用户名" size="large" v-model="data.username" autocomplete="off">
<template #prefix>
<el-icon>
<Iphone />
@ -206,14 +125,7 @@
</el-tabs>
<div class="block">
<el-input
placeholder="请输入密码(8-16位)"
maxlength="16"
size="large"
v-model="data.password"
show-password
autocomplete="off"
>
<el-input placeholder="请输入密码(8-16位)" maxlength="16" size="large" v-model="data.password" show-password autocomplete="off">
<template #prefix>
<el-icon>
<Lock />
@ -223,14 +135,7 @@
</div>
<div class="block">
<el-input
placeholder="重复密码(8-16位)"
size="large"
maxlength="16"
v-model="data.repass"
show-password
autocomplete="off"
>
<el-input placeholder="重复密码(8-16位)" size="large" maxlength="16" v-model="data.repass" show-password autocomplete="off">
<template #prefix>
<el-icon>
<Lock />
@ -240,12 +145,7 @@
</div>
<div class="block">
<el-input
placeholder="邀请码(可选)"
size="large"
v-model="data.invite_code"
autocomplete="off"
>
<el-input placeholder="邀请码(可选)" size="large" v-model="data.invite_code" autocomplete="off">
<template #prefix>
<el-icon>
<Message />
@ -254,23 +154,14 @@
</el-input>
</div>
<el-row class="btn-row" :gutter="20">
<el-col :span="12">
<el-button
class="login-btn"
type="primary"
size="large"
@click="submitRegister"
>注册</el-button
>
</el-col>
<el-col :span="12">
<div class="text">
已有账号
<el-tag @click="login = true">登录</el-tag>
</div>
</el-col>
</el-row>
<div class="w-full">
<el-button class="login-btn w-full" type="primary" size="large" @click="submitRegister"> </el-button>
</div>
<div class="text text-sm flex justify-center items-center w-full pt-3">
已有账号
<el-button size="small" @click="login = true">登录</el-button>
</div>
</el-form>
<div class="tip-result" v-else>
@ -291,11 +182,10 @@
</el-row>
</div>
</div>
<captcha v-if="enableVerify" @success="submit" ref="captchaRef" />
<reset-pass @hide="showResetPass = false" :show="showResetPass" />
</el-dialog>
</div>
</template>
<script setup>
@ -316,7 +206,7 @@ import { useSharedStore } from "@/store/sharedata";
// eslint-disable-next-line no-undef
const props = defineProps({
show: Boolean
show: Boolean,
});
const showDialog = ref(false);
watch(
@ -334,7 +224,7 @@ const data = ref({
email: "",
repass: "",
code: "",
invite_code: ""
invite_code: "",
});
const enableMobile = ref(false);
const enableEmail = ref(false);
@ -351,8 +241,6 @@ const enableVerify = ref(false);
const showResetPass = ref(false);
const router = useRouter();
const store = useSharedStore();
//
const needVerify = ref(false);
onMounted(() => {
const returnURL = `${location.protocol}//${location.host}/login/callback?action=login`;
@ -410,7 +298,7 @@ const submitLogin = () => {
if (data.value.password === "") {
return ElMessage.error("请输入密码");
}
if (enableVerify.value && needVerify.value) {
if (enableVerify.value) {
captchaRef.value.loadCaptcha();
action.value = "login";
} else {
@ -429,11 +317,9 @@ const doLogin = (verifyData) => {
ElMessage.success("登录成功!");
emits("hide");
emits("success");
needVerify.value = false;
})
.catch((e) => {
ElMessage.error("登录失败," + e.message);
needVerify.value = true;
});
};
@ -458,10 +344,7 @@ const submitRegister = () => {
return ElMessage.error("两次输入密码不一致");
}
if (
(activeName.value === "mobile" || activeName.value === "email") &&
data.value.code === ""
) {
if ((activeName.value === "mobile" || activeName.value === "email") && data.value.code === "") {
return ElMessage.error("请输入验证码");
}
if (enableVerify.value && activeName.value === "username") {
@ -486,52 +369,26 @@ const doRegister = (verifyData) => {
emits("hide");
emits("success");
},
duration: 1000
duration: 1000,
});
})
.catch((e) => {
ElMessage.error("注册失败," + e.message);
});
};
const close = function () {
emits("hide", false);
login.value = true;
};
</script>
<style lang="stylus">
.login-dialog {
border-radius 10px
max-width 600px
.header {
position relative
.title {
padding 0
font-size 18px
}
.close-icon {
cursor pointer
position absolute
right 0
top 0
font-weight normal
font-size 20px
&:hover {
color #20a0ff
}
}
.el-tabs__nav {
display flex
width 100%
justify-content space-between
}
.el-dialog__body {
padding 10px 20px 20px 20px
}
.form {
.block {
margin-bottom 10px
@ -541,6 +398,7 @@ const close = function () {
display flex
.login-btn {
font-size 16px
width 100%
}
@ -566,7 +424,6 @@ const close = function () {
align-items: center;
}
.login-type {
padding 15px
display flex
justify-content center
@ -582,14 +439,8 @@ const close = function () {
}
}
.reg {
height 50px
display flex
align-items center
.el-button {
margin-left 10px
}
.text {
color var(--el-text-color-primary)
}
}

View File

@ -1,13 +1,6 @@
<template>
<div class="reset-pass">
<el-dialog
v-model="showDialog"
:close-on-click-modal="true"
width="540px"
:before-close="close"
:title="title"
class="reset-pass-dialog"
>
<el-dialog v-model="showDialog" :close-on-click-modal="true" width="500px" :before-close="close" :title="title" class="reset-pass-dialog">
<div class="form">
<el-form :model="form" label-width="80px" label-position="left">
<el-tabs v-model="form.type" class="demo-tabs">
@ -16,14 +9,10 @@
<el-input v-model="form.mobile" placeholder="请输入手机号" />
</el-form-item>
<el-form-item label="验证码">
<el-row class="code-row">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6" />
</el-col>
<el-col :span="8" class="send-button">
<send-msg size="" :receiver="form.mobile" type="mobile" />
</el-col>
</el-row>
<div class="flex">
<el-input v-model="form.code" maxlength="6" class="mr-2 w-1/2" />
<send-msg size="" :receiver="form.mobile" type="mobile" />
</div>
</el-form-item>
</el-tab-pane>
<el-tab-pane label="邮箱验证" name="email">
@ -31,14 +20,10 @@
<el-input v-model="form.email" placeholder="请输入邮箱地址" />
</el-form-item>
<el-form-item label="验证码">
<el-row class="code-row">
<el-col :span="16">
<el-input v-model="form.code" maxlength="6" />
</el-col>
<el-col :span="8" class="send-button">
<send-msg size="" :receiver="form.email" type="email" />
</el-col>
</el-row>
<div class="flex">
<el-input v-model="form.code" maxlength="6" class="mr-2 w-1/2" />
<send-msg size="" :receiver="form.email" type="email" />
</div>
</el-form-item>
</el-tab-pane>
</el-tabs>
@ -70,7 +55,7 @@ import { validateEmail, validateMobile } from "@/utils/validate";
const props = defineProps({
show: Boolean,
mobile: String
mobile: String,
});
const showDialog = computed(() => {
@ -84,7 +69,7 @@ const form = ref({
type: "mobile",
code: "",
password: "",
repass: ""
repass: "",
});
const emits = defineEmits(["hide"]);
@ -105,7 +90,7 @@ const save = () => {
ElMessage.success({
message: "重置密码成功",
duration: 1000,
onClose: () => emits("hide", false)
onClose: () => emits("hide", false),
});
})
.catch((e) => {

View File

@ -0,0 +1,172 @@
<template>
<el-dialog v-model="show" :fullscreen="true" @close="close" style="--el-dialog-border-radius: 0px">
<template #header>
<div class="header">
<h3 style="color: var(--text-theme-color)">绘画任务详情</h3>
</div>
</template>
<el-row :gutter="20">
<el-col :span="16">
<div class="img-container">
<el-image :src="item['img_url']" fit="contain">
<template #placeholder>
<div class="image-slot">正在加载图片</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<i class="iconfont icon-image"></i>
</el-icon>
</div>
</template>
</el-image>
</div>
</el-col>
<el-col :span="8">
<div class="task-info">
<div class="info-line">
<el-divider> 正向提示词 </el-divider>
<div class="prompt">
<span>{{ item.prompt }}</span>
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.prompt">
<i class="iconfont icon-copy"></i>
</el-icon>
</div>
</div>
<div class="info-line">
<el-divider> 反向提示词 </el-divider>
<div class="prompt">
<span>{{ item.params.negative_prompt }}</span>
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.params.negative_prompt">
<i class="iconfont icon-copy"></i>
</el-icon>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>采样方法</label>
<div class="item-value">{{ item.params.sampler }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>图片尺寸</label>
<div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.steps }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>引导系数</label>
<div class="item-value">{{ item.params.cfg_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>随机因子</label>
<div class="item-value">{{ item.params.seed }}</div>
</div>
</div>
<div v-if="item.params.hd_fix">
<el-divider> 高清修复 </el-divider>
<div class="info-line">
<div class="wrapper">
<label>重绘幅度</label>
<div class="item-value">{{ item.params.hd_redraw_rate }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大算法</label>
<div class="item-value">{{ item.params.hd_scale_alg }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大倍数</label>
<div class="item-value">{{ item.params.hd_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.hd_steps }}</div>
</div>
</div>
</div>
<div class="copy-params">
<el-button type="primary" round @click="drawSame(item)">画一张同款的</el-button>
</div>
</div>
</el-col>
</el-row>
</el-dialog>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import Clipboard from "clipboard";
import { showMessageOK, showMessageError } from "@/utils/dialog";
const props = defineProps({
modelValue: Boolean,
data: Object,
});
const item = ref(props.data);
const show = ref(props.modelValue);
const emit = defineEmits(["drawSame", "close"]);
const clipboard = ref(null);
onMounted(() => {
clipboard.value = new Clipboard(".copy-prompt-wall");
clipboard.value.on("success", () => {
showMessageOK("复制成功!");
});
clipboard.value.on("error", () => {
showMessageError("复制失败!");
});
});
watch(
() => props.modelValue,
(newValue) => {
show.value = newValue;
}
);
watch(
() => props.data,
(newValue) => {
item.value = newValue;
}
);
const drawSame = (item) => {
emit("drawSame", item);
};
const close = () => {
emit("close");
};
</script>
<style lang="stylus" scoped></style>

View File

@ -1,11 +1,6 @@
<template>
<el-container class="send-verify-code">
<el-button
type="success"
:size="props.size"
:disabled="!canSend"
@click="sendMsg"
>
<el-button type="success" :size="props.size" :disabled="!canSend" @click="sendMsg">
{{ btnText }}
</el-button>
@ -15,12 +10,12 @@
<script setup>
//
import {ref} from "vue";
import {validateEmail, validateMobile} from "@/utils/validate";
import {httpPost} from "@/utils/http";
import {showMessageError, showMessageOK} from "@/utils/dialog";
import { ref } from "vue";
import { validateEmail, validateMobile } from "@/utils/validate";
import { httpPost } from "@/utils/http";
import { ElMessage } from "element-plus";
import Captcha from "@/components/Captcha.vue";
import {getSystemInfo} from "@/store/cache";
import { getSystemInfo } from "@/store/cache";
// eslint-disable-next-line no-undef
const props = defineProps({
@ -28,8 +23,8 @@ const props = defineProps({
size: String,
type: {
type: String,
default: "mobile"
}
default: "mobile",
},
});
const btnText = ref("发送验证码");
const canSend = ref(true);
@ -42,10 +37,10 @@ getSystemInfo().then((res) => {
const sendMsg = () => {
if (!validateMobile(props.receiver) && props.type === "mobile") {
return showMessageError("请输入合法的手机号");
return ElMessage.error("请输入合法的手机号");
}
if (!validateEmail(props.receiver) && props.type === "email") {
return showMessageError("请输入合法的邮箱地址");
return ElMessage.error("请输入合法的邮箱地址");
}
if (enableVerify.value) {
@ -65,10 +60,10 @@ const doSendMsg = (data) => {
receiver: props.receiver,
key: data.key,
dots: data.dots,
x: data.x
x: data.x,
})
.then(() => {
showMessageOK("验证码发送成功");
ElMessage.success("验证码发送成功");
let time = 60;
btnText.value = time;
const handler = setInterval(() => {
@ -84,7 +79,7 @@ const doSendMsg = (data) => {
})
.catch((e) => {
canSend.value = true;
showMessageError("验证码发送失败:" + e.message);
ElMessage.error("验证码发送失败:" + e.message);
});
};
</script>

View File

@ -1,169 +1,177 @@
<template>
<div class="slide-captcha">
<div class="bg-img">
<el-image :src="backgroundImg" />
<div :class="verifyMsgClass" v-if="checked !== 0">
<span v-if="checked ===1">{{time}}s</span>
{{verifyMsg}}
<div class="flex justify-center items-center">
<div class="slide-captcha">
<div class="bg-img">
<el-image :src="backgroundImg" />
<div :class="verifyMsgClass" v-if="checked !== 0">
<span v-if="checked === 1">{{ time }}s</span>
{{ verifyMsg }}
</div>
<div class="refresh" @click="emits('refresh')">
<el-icon><Refresh /></el-icon>
</div>
<span class="block">
<el-image :src="blockImg" :style="{ left: blockLeft + 'px' }" />
</span>
</div>
<div class="refresh" @click="emits('refresh')">
<el-icon><Refresh /></el-icon>
</div>
<span class="block">
<el-image :src="blockImg" :style="{left: blockLeft+'px'}" />
</span>
</div>
<div class="verify">
<div class="verify-bar-area">
<span class="verify-msg">{{verifyText}}</span>
<div class="verify">
<div class="verify-bar-area">
<span class="verify-msg">{{ verifyText }}</span>
<div :class="leftBarClass" :style="{width: leftBarWidth+'px'}">
<div :class="blockClass" id="dragBlock"
:style="{left: blockLeft+'px'}">
<el-icon v-if="checked === 0"><ArrowRightBold /></el-icon>
<el-icon v-if="checked === 1"><CircleCheckFilled /></el-icon>
<el-icon v-if="checked === 2"><CircleCloseFilled /></el-icon>
<div :class="leftBarClass" :style="{ width: leftBarWidth + 'px' }">
<div :class="blockClass" id="dragBlock" :style="{ left: blockLeft + 'px' }">
<el-icon v-if="checked === 0"><ArrowRightBold /></el-icon>
<el-icon v-if="checked === 1"><CircleCheckFilled /></el-icon>
<el-icon v-if="checked === 2"><CircleCloseFilled /></el-icon>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
// eslint-disable-next-line no-undef
import {onMounted, ref, watch} from "vue";
import {ArrowRightBold, CircleCheckFilled, CircleCloseFilled, Refresh} from "@element-plus/icons-vue";
import { onMounted, ref, watch } from "vue";
import { ArrowRightBold, CircleCheckFilled, CircleCloseFilled, Refresh } from "@element-plus/icons-vue";
// eslint-disable-next-line no-undef
const props = defineProps({
bgImg: String,
bkImg: String,
result: Number,
})
const verifyText = ref('向右滑动完成验证')
const verifyMsg = ref('')
const verifyMsgClass = ref("verify-text success")
const blockClass = ref('verify-move-block')
const leftBarClass = ref('verify-left-bar')
const backgroundImg = ref('')
const blockImg = ref('')
const leftBarWidth = ref(0)
const blockLeft = ref(0)
const checked = ref(0)
const time = ref('')
watch(() => props.bgImg, (newVal) => {
backgroundImg.value = newVal;
});
watch(() => props.bkImg, (newVal) => {
blockImg.value = newVal;
});
watch(() => props.result, (newVal) => {
checked.value = newVal;
if (newVal === 1) {
verifyMsgClass.value = "verify-text success"
blockClass.value = 'verify-move-block success'
leftBarClass.value = 'verify-left-bar success'
verifyMsg.value = '验证成功'
setTimeout(() => emits('hide'), 1000)
} else if (newVal ===2) {
verifyMsgClass.value = "verify-text error"
blockClass.value = 'verify-move-block error'
leftBarClass.value = 'verify-left-bar error'
verifyMsg.value = '验证失败'
setTimeout(() => {
reset()
emits('refresh')
}, 1000)
} else {
reset()
const verifyText = ref("向右滑动完成验证");
const verifyMsg = ref("");
const verifyMsgClass = ref("verify-text success");
const blockClass = ref("verify-move-block");
const leftBarClass = ref("verify-left-bar");
const backgroundImg = ref("");
const blockImg = ref("");
const leftBarWidth = ref(0);
const blockLeft = ref(0);
const checked = ref(0);
const time = ref("");
watch(
() => props.bgImg,
(newVal) => {
backgroundImg.value = newVal;
}
});
);
watch(
() => props.bkImg,
(newVal) => {
blockImg.value = newVal;
}
);
watch(
() => props.result,
(newVal) => {
checked.value = newVal;
if (newVal === 1) {
verifyMsgClass.value = "verify-text success";
blockClass.value = "verify-move-block success";
leftBarClass.value = "verify-left-bar success";
verifyMsg.value = "验证成功";
setTimeout(() => emits("hide"), 1000);
} else if (newVal === 2) {
verifyMsgClass.value = "verify-text error";
blockClass.value = "verify-move-block error";
leftBarClass.value = "verify-left-bar error";
verifyMsg.value = "验证失败";
setTimeout(() => {
reset();
emits("refresh");
}, 1000);
} else {
reset();
}
}
);
// eslint-disable-next-line no-undef
const emits = defineEmits(['confirm','refresh','hide']);
const emits = defineEmits(["confirm", "refresh", "hide"]);
let offsetX = 0, isDragging = false
let start = 0
let offsetX = 0,
isDragging = false;
let start = 0;
onMounted(() => {
const dragBlock = document.getElementById('dragBlock');
dragBlock.addEventListener('mousedown', (evt) => {
blockClass.value = 'verify-move-block active'
leftBarClass.value = 'verify-left-bar active'
leftBarWidth.value = 32
isDragging = true
verifyText.value = ""
offsetX = evt.clientX
start = new Date().getTime()
const dragBlock = document.getElementById("dragBlock");
dragBlock.addEventListener("mousedown", (evt) => {
blockClass.value = "verify-move-block active";
leftBarClass.value = "verify-left-bar active";
leftBarWidth.value = 32;
isDragging = true;
verifyText.value = "";
offsetX = evt.clientX;
start = new Date().getTime();
evt.preventDefault();
})
});
document.body.addEventListener('mousemove',(evt) => {
document.body.addEventListener("mousemove", (evt) => {
if (!isDragging) {
return
return;
}
const x = Math.max(evt.clientX - offsetX, 0)
const x = Math.max(evt.clientX - offsetX, 0);
blockLeft.value = x;
leftBarWidth.value = x + 32
})
leftBarWidth.value = x + 32;
});
document.body.addEventListener('mouseup', () => {
document.body.addEventListener("mouseup", () => {
if (!isDragging) {
return
return;
}
time.value = ((new Date().getTime() - start)/1000).toFixed(2)
isDragging = false
emits('confirm', Math.floor(blockLeft.value))
})
time.value = ((new Date().getTime() - start) / 1000).toFixed(2);
isDragging = false;
emits("confirm", Math.floor(blockLeft.value));
});
//
dragBlock.addEventListener('touchstart', function (e) {
dragBlock.addEventListener("touchstart", function (e) {
isDragging = true;
blockClass.value = 'verify-move-block active'
leftBarClass.value = 'verify-left-bar active'
leftBarWidth.value = 32
isDragging = true
verifyText.value = ""
blockClass.value = "verify-move-block active";
leftBarClass.value = "verify-left-bar active";
leftBarWidth.value = 32;
isDragging = true;
verifyText.value = "";
offsetX = e.touches[0].clientX - dragBlock.getBoundingClientRect().left;
start = new Date().getTime()
start = new Date().getTime();
e.preventDefault();
});
document.addEventListener('touchmove', function (e) {
document.addEventListener("touchmove", function (e) {
if (!isDragging) {
return
return;
}
e.preventDefault();
const x = Math.max(e.touches[0].clientX - offsetX, 0)
const x = Math.max(e.touches[0].clientX - offsetX, 0);
blockLeft.value = x;
leftBarWidth.value = x + 32
leftBarWidth.value = x + 32;
});
document.addEventListener('touchend', function () {
document.addEventListener("touchend", function () {
if (!isDragging) {
return
return;
}
time.value = ((new Date().getTime() - start)/1000).toFixed(2)
isDragging = false
emits('confirm', Math.floor(blockLeft.value))
time.value = ((new Date().getTime() - start) / 1000).toFixed(2);
isDragging = false;
emits("confirm", Math.floor(blockLeft.value));
});
})
});
//
const reset = () => {
blockClass.value = 'verify-move-block'
leftBarClass.value = 'verify-left-bar'
leftBarWidth.value = 0
blockLeft.value = 0
checked.value = 0
verifyText.value = "向右滑动完成验证"
}
blockClass.value = "verify-move-block";
leftBarClass.value = "verify-left-bar";
leftBarWidth.value = 0;
blockLeft.value = 0;
checked.value = 0;
verifyText.value = "向右滑动完成验证";
};
</script>
<style scoped lang="stylus">
@ -177,6 +185,7 @@ const reset = () => {
}
.slide-captcha {
width 310px
* {
margin 0
padding 0
@ -294,4 +303,4 @@ const reset = () => {
}
}
}
</style>
</style>

View File

@ -1,7 +1,7 @@
<template>
<div :class="'sidebar ' + theme">
<a class="logo w-full" href="/" target="_blank">
<el-image :src="logo" />
<a class="logo w-full flex items-center" href="/" target="_blank">
<img :src="logo" style="height: 36px" />
<span class="text" v-show="!sidebar.collapse">{{ title }}</span>
</a>

View File

@ -5,14 +5,14 @@
// * @Author yangjian102621@163.com
// * +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
import { createRouter, createWebHistory } from "vue-router";
import {createRouter, createWebHistory} from "vue-router";
const routes = [
{
name: "Index",
path: "/",
meta: { title: "首页" },
component: () => import("@/views/Index.vue")
component: () => import("@/views/Index.vue"),
},
{
name: "home",
@ -24,136 +24,136 @@ const routes = [
name: "chat",
path: "/chat",
meta: { title: "创作中心" },
component: () => import("@/views/ChatPlus.vue")
component: () => import("@/views/ChatPlus.vue"),
},
{
name: "chat-id",
path: "/chat/:id",
meta: { title: "创作中心" },
component: () => import("@/views/ChatPlus.vue")
component: () => import("@/views/ChatPlus.vue"),
},
{
name: "image-mj",
path: "/mj",
meta: { title: "MidJourney 绘画中心" },
component: () => import("@/views/ImageMj.vue")
component: () => import("@/views/ImageMj.vue"),
},
{
name: "image-sd",
path: "/sd",
meta: { title: "stable diffusion 绘画中心" },
component: () => import("@/views/ImageSd.vue")
component: () => import("@/views/ImageSd.vue"),
},
{
name: "member",
path: "/member",
meta: { title: "会员充值中心" },
component: () => import("@/views/Member.vue")
component: () => import("@/views/Member.vue"),
},
{
name: "chat-app",
path: "/apps",
meta: { title: "应用中心" },
component: () => import("@/views/ChatApps.vue")
component: () => import("@/views/ChatApps.vue"),
},
{
name: "images",
path: "/images-wall",
meta: { title: "作品展示" },
component: () => import("@/views/ImagesWall.vue")
component: () => import("@/views/ImagesWall.vue"),
},
{
name: "user-invitation",
path: "/invite",
meta: { title: "推广计划" },
component: () => import("@/views/Invitation.vue")
component: () => import("@/views/Invitation.vue"),
},
{
name: "powerLog",
path: "/powerLog",
meta: { title: "消费日志" },
component: () => import("@/views/PowerLog.vue")
component: () => import("@/views/PowerLog.vue"),
},
{
name: "xmind",
path: "/xmind",
meta: { title: "思维导图" },
component: () => import("@/views/MarkMap.vue")
component: () => import("@/views/MarkMap.vue"),
},
{
name: "dalle",
path: "/dalle",
meta: { title: "DALLE-3" },
component: () => import("@/views/Dalle.vue")
component: () => import("@/views/Dalle.vue"),
},
{
name: "suno",
path: "/suno",
meta: { title: "Suno音乐创作" },
component: () => import("@/views/Suno.vue")
component: () => import("@/views/Suno.vue"),
},
{
name: "ExternalLink",
path: "/external",
component: () => import("@/views/ExternalPage.vue")
component: () => import("@/views/ExternalPage.vue"),
},
{
name: "song",
path: "/song/:id",
meta: { title: "Suno音乐播放" },
component: () => import("@/views/Song.vue")
component: () => import("@/views/Song.vue"),
},
{
name: "luma",
path: "/luma",
meta: { title: "Luma视频创作" },
component: () => import("@/views/Luma.vue")
}
]
component: () => import("@/views/Luma.vue"),
},
],
},
{
name: "chat-export",
path: "/chat/export",
meta: { title: "导出会话记录" },
component: () => import("@/views/ChatExport.vue")
component: () => import("@/views/ChatExport.vue"),
},
{
name: "login",
path: "/login",
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")
component: () => import("@/views/LoginCallback.vue"),
},
{
name: "register",
path: "/register",
meta: { title: "用户注册" },
component: () => import("@/views/Register.vue")
component: () => import("@/views/Register.vue"),
},
{
name: "resetpassword",
path: "/resetpassword",
meta: { title: "重置密码" },
component: () => import("@/views/Resetpassword.vue")
component: () => import("@/views/Resetpassword.vue"),
},
{
path: "/admin/login",
name: "admin-login",
meta: { title: "控制台登录" },
component: () => import("@/views/admin/Login.vue")
component: () => import("@/views/admin/Login.vue"),
},
{
path: "/payReturn",
name: "pay-return",
meta: { title: "支付回调" },
component: () => import("@/views/PayReturn.vue")
component: () => import("@/views/PayReturn.vue"),
},
{
name: "admin",
@ -166,118 +166,112 @@ const routes = [
path: "/admin/dashboard",
name: "admin-dashboard",
meta: { title: "仪表盘" },
component: () => import("@/views/admin/Dashboard.vue")
component: () => import("@/views/admin/Dashboard.vue"),
},
{
path: "/admin/system",
name: "admin-system",
meta: { title: "系统设置" },
component: () => import("@/views/admin/SysConfig.vue")
component: () => import("@/views/admin/SysConfig.vue"),
},
{
path: "/admin/user",
name: "admin-user",
meta: { title: "用户管理" },
component: () => import("@/views/admin/Users.vue")
component: () => import("@/views/admin/Users.vue"),
},
{
path: "/admin/app",
name: "admin-app",
meta: { title: "应用列表" },
component: () => import("@/views/admin/Apps.vue")
component: () => import("@/views/admin/Apps.vue"),
},
{
path: "/admin/app/type",
name: "admin-app-type",
meta: { title: "应用分类" },
component: () => import("@/views/admin/AppType.vue")
component: () => import("@/views/admin/AppType.vue"),
},
{
path: "/admin/apikey",
name: "admin-apikey",
meta: { title: "API-KEY 管理" },
component: () => import("@/views/admin/ApiKey.vue")
component: () => import("@/views/admin/ApiKey.vue"),
},
{
path: "/admin/chat/model",
name: "admin-chat-model",
meta: { title: "语言模型" },
component: () => import("@/views/admin/ChatModel.vue")
component: () => import("@/views/admin/ChatModel.vue"),
},
{
path: "/admin/product",
name: "admin-product",
meta: { title: "充值产品" },
component: () => import("@/views/admin/Product.vue")
component: () => import("@/views/admin/Product.vue"),
},
{
path: "/admin/order",
name: "admin-order",
meta: { title: "充值订单" },
component: () => import("@/views/admin/Order.vue")
component: () => import("@/views/admin/Order.vue"),
},
{
path: "/admin/redeem",
name: "admin-redeem",
meta: { title: "兑换码管理" },
component: () => import("@/views/admin/Redeem.vue")
component: () => import("@/views/admin/Redeem.vue"),
},
{
path: "/admin/loginLog",
name: "admin-loginLog",
meta: { title: "登录日志" },
component: () => import("@/views/admin/LoginLog.vue")
component: () => import("@/views/admin/LoginLog.vue"),
},
{
path: "/admin/functions",
name: "admin-functions",
meta: { title: "函数管理" },
component: () => import("@/views/admin/Functions.vue")
component: () => import("@/views/admin/Functions.vue"),
},
{
path: "/admin/chats",
name: "admin-chats",
meta: { title: "对话管理" },
component: () => import("@/views/admin/ChatList.vue")
component: () => import("@/views/admin/ChatList.vue"),
},
{
path: "/admin/images",
name: "admin-images",
meta: { title: "绘图管理" },
component: () => import("@/views/admin/ImageList.vue")
component: () => import("@/views/admin/ImageList.vue"),
},
{
path: "/admin/medias",
name: "admin-medias",
meta: { title: "音视频管理" },
component: () => import("@/views/admin/Medias.vue")
component: () => import("@/views/admin/Medias.vue"),
},
{
path: "/admin/powerLog",
name: "admin-power-log",
meta: { title: "算力日志" },
component: () => import("@/views/admin/PowerLog.vue")
component: () => import("@/views/admin/PowerLog.vue"),
},
{
path: "/admin/manger",
name: "admin-manger",
meta: { title: "管理员" },
component: () => import("@/views/admin/Manager.vue")
}
]
component: () => import("@/views/admin/Manager.vue"),
},
],
},
{
name: "mobile-login",
path: "/mobile/login",
meta: { title: "用户登录" },
component: () => import("@/views/Login.vue")
},
{
name: "mobile-register",
path: "/mobile/register",
meta: { title: "用户注册" },
component: () => import("@/views/Register.vue")
component: () => import("@/views/mobile/Login.vue"),
},
{
name: "mobile",
@ -289,65 +283,65 @@ const routes = [
{
path: "/mobile/index",
name: "mobile-index",
component: () => import("@/views/mobile/Index.vue")
component: () => import("@/views/mobile/Index.vue"),
},
{
path: "/mobile/chat",
name: "mobile-chat",
component: () => import("@/views/mobile/ChatList.vue")
component: () => import("@/views/mobile/ChatList.vue"),
},
{
path: "/mobile/image",
name: "mobile-image",
component: () => import("@/views/mobile/Image.vue")
component: () => import("@/views/mobile/Image.vue"),
},
{
path: "/mobile/profile",
name: "mobile-profile",
component: () => import("@/views/mobile/Profile.vue")
component: () => import("@/views/mobile/Profile.vue"),
},
{
path: "/mobile/imgWall",
name: "mobile-img-wall",
component: () => import("@/views/mobile/pages/ImgWall.vue")
component: () => import("@/views/mobile/pages/ImgWall.vue"),
},
{
path: "/mobile/chat/session",
name: "mobile-chat-session",
component: () => import("@/views/mobile/ChatSession.vue")
component: () => import("@/views/mobile/ChatSession.vue"),
},
{
path: "/mobile/chat/export",
name: "mobile-chat-export",
component: () => import("@/views/mobile/ChatExport.vue")
}
]
component: () => import("@/views/mobile/ChatExport.vue"),
},
],
},
{
name: "test",
path: "/test",
meta: { title: "测试页面" },
component: () => import("@/views/Test.vue")
component: () => import("@/views/Test.vue"),
},
{
name: "test2",
path: "/test2",
meta: { title: "测试页面" },
component: () => import("@/views/RealtimeTest.vue")
component: () => import("@/views/RealtimeTest.vue"),
},
{
name: "NotFound",
path: "/:all(.*)",
meta: { title: "页面没有找到" },
component: () => import("@/views/404.vue")
}
component: () => import("@/views/404.vue"),
},
];
// console.log(MY_VARIABLE)
const router = createRouter({
history: createWebHistory(),
routes: routes
routes: routes,
});
let prevRoute = null;

View File

@ -1,51 +1,51 @@
/**
* Util lib functions
*/
import {showConfirmDialog, showFailToast, showSuccessToast, showToast, showLoadingToast, closeToast} from "vant";
import {closeToast, showConfirmDialog, showFailToast, showLoadingToast, showSuccessToast, showToast} from "vant";
import {isMobile} from "@/utils/libs";
import {ElMessage} from "element-plus";
export function showLoginDialog(router) {
showConfirmDialog({
title: '登录',
message:
'此操作需要登录才能进行,前往登录?',
}).then(() => {
router.push("/login")
}).catch(() => {
showConfirmDialog({
title: "登录",
message: "此操作需要登录才能进行,前往登录?",
})
.then(() => {
router.push("/login");
})
.catch(() => {
// on cancel
});
}
export function showMessageOK(message) {
if (isMobile()) {
showSuccessToast(message)
} else {
ElMessage.success(message)
}
if (isMobile()) {
showSuccessToast(message);
} else {
ElMessage.success(message);
}
}
export function showMessageInfo(message) {
if (isMobile()) {
showToast(message)
} else {
ElMessage.info(message)
}
if (isMobile()) {
showToast(message);
} else {
ElMessage.info(message);
}
}
export function showMessageError(message) {
if (isMobile()) {
showFailToast(message)
} else {
ElMessage.error(message)
}
if (isMobile()) {
showFailToast({message: message, duration: 0});
} else {
ElMessage.error(message);
}
}
export function showLoading(message = '正在处理...') {
showLoadingToast({ message: message, forbidClick: true, duration: 0 })
}
export function closeLoading() {
closeToast()
}
export function showLoading(message = "正在处理...") {
showLoadingToast({message: message, forbidClick: true, duration: 0});
}
export function closeLoading() {
closeToast();
}

View File

@ -9,8 +9,10 @@
</div>
<div class="flex" :class="{ 'top-collapse': !isCollapse }">
<div class="top-avatar flex">
<span class="title" v-if="!isCollapse">GeekAI</span>
<img v-if="loginUser.id" :src="!!loginUser.avatar ? loginUser.avatar : avatarImg" alt="" :class="{ marr: !isCollapse }" />
<span class="title" v-if="!isCollapse">{{ title }}</span>
<el-tooltip :content="title" placement="right">
<img :src="logo" alt="" :class="{ marr: !isCollapse }" v-if="isCollapse" />
</el-tooltip>
</div>
<div class="menuIcon xxx" @click="isCollapse = !isCollapse">
<el-tooltip content="关闭菜单" placement="right" v-if="!isCollapse">
@ -80,6 +82,9 @@
</template>
<template #default>
<ul class="more-menus setting-menus">
<li>
<img :src="loginUser.avatar ? loginUser.avatar : avatarImg" />
</li>
<li>
<div @click="showConfigDialog = true" class="flex">
<el-icon>
@ -113,12 +118,6 @@
</div>
</div>
<el-scrollbar class="right-main">
<div
v-if="loginUser.id === undefined || !loginUser.id"
class="loginMask"
:style="{ left: isCollapse ? '65px' : '170px' }"
@click="showNoticeLogin = true"
></div>
<div class="topheader" v-if="loginUser.id === undefined || !loginUser.id">
<el-button @click="router.push('/login')" class="btn-go animate__animated animate__pulse animate__infinite" round>登录</el-button>
</div>
@ -132,12 +131,14 @@
<!-- </div> -->
</el-scrollbar>
<config-dialog v-if="loginUser.id" :show="showConfigDialog" @hide="showConfigDialog = false" />
<el-dialog v-model="showNoticeLogin">
<el-result icon="warning" title="未登录" sub-title="登录后解锁功能">
<template #extra>
<el-button type="primary" @click="router.push('/login')">登录</el-button>
</template>
</el-result>
<el-dialog v-model="showLoginDialog" width="500px" @close="store.setShowLoginDialog(false)">
<template #header>
<div class="text-center text-xl" style="color: var(--theme-text-color-primary)">登录后解锁功能</div>
</template>
<div class="p-4 pt-2 pb-2">
<LoginDialog @success="loginSuccess" @hide="store.setShowLoginDialog(false)" />
</div>
</el-dialog>
</div>
</template>
@ -145,7 +146,6 @@
<script setup>
import { CirclePlus, Setting, UserFilled } from "@element-plus/icons-vue";
import ThemeChange from "@/components/ThemeChange.vue";
import avatarImg from "@/assets/img/avatar.jpg";
import { useRouter } from "vue-router";
import { onMounted, ref, watch } from "vue";
import { httpGet } from "@/utils/http";
@ -155,6 +155,8 @@ import { removeUserToken } from "@/store/session";
import { useSharedStore } from "@/store/sharedata";
import ConfigDialog from "@/components/UserInfoDialog.vue";
import { showMessageError } from "@/utils/dialog";
import LoginDialog from "@/components/LoginDialog.vue";
import { substr } from "@/utils/libs";
const isCollapse = ref(true);
const router = useRouter();
@ -164,7 +166,14 @@ const moreNavs = ref([]);
const curPath = ref();
const title = ref("");
const showNoticeLogin = ref(false);
const avatarImg = ref("/images/avatar/default.jpg");
const store = useSharedStore();
const loginUser = ref({});
const routerViewKey = ref(0);
const showConfigDialog = ref(false);
const license = ref({ de_copy: true });
const gitURL = ref(process.env.VUE_APP_GIT_URL);
const showLoginDialog = ref(false);
/**
* 从路径名中提取第一个路径段
@ -190,18 +199,11 @@ const getFirstPathSegment = (url) => {
return null;
}
};
const loginUser = ref({});
const routerViewKey = ref(0);
const showConfigDialog = ref(false);
const license = ref({ de_copy: true });
const gitURL = ref(process.env.VUE_APP_GIT_URL);
const store = useSharedStore();
const show = ref(false);
watch(
() => store.showLoginDialog,
(newValue) => {
show.value = newValue;
showLoginDialog.value = newValue;
}
);
@ -216,7 +218,6 @@ if (curPath.value === "/external") {
}
const changeNav = (item) => {
curPath.value = item.url;
console.log(item.url);
if (item.url.indexOf("http") !== -1) {
//
router.push({ path: "/external", query: { url: item.url, title: item.name } });
@ -280,16 +281,19 @@ const logout = function () {
httpGet("/api/user/logout")
.then(() => {
removeUserToken();
store.setShowLoginDialog(true);
store.setIsLogin(false);
loginUser.value = {};
//
routerViewKey.value += 1;
router.push("/login");
})
.catch(() => {
ElMessage.error("注销失败!");
});
};
const loginSuccess = () => {
init();
store.setShowLoginDialog(false);
//
routerViewKey.value += 1;
};
</script>
<style lang="stylus" scoped>

View File

@ -311,110 +311,7 @@
</div>
<!-- 任务详情弹框 -->
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
<el-row :gutter="20">
<el-col :span="16">
<div class="img-container" :style="{ maxHeight: fullImgHeight + 'px' }">
<el-image :src="item['img_url']" fit="contain" />
</div>
</el-col>
<el-col :span="8">
<div class="task-info">
<div class="info-line">
<el-divider> 正向提示词 </el-divider>
<div class="prompt">
<span>{{ item.prompt }}</span>
<el-icon class="copy-prompt-sd" :data-clipboard-text="item.prompt">
<DocumentCopy />
</el-icon>
</div>
</div>
<div class="info-line">
<el-divider> 反向提示词 </el-divider>
<div class="prompt">
<span>{{ item.params.neg_prompt }}</span>
<el-icon class="copy-prompt-sd" :data-clipboard-text="item.params.neg_prompt">
<DocumentCopy />
</el-icon>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>采样方法</label>
<div class="item-value">{{ item.params.sampler }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>图片尺寸</label>
<div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.steps }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>引导系数</label>
<div class="item-value">{{ item.params.cfg_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>随机因子</label>
<div class="item-value">{{ item.params.seed }}</div>
</div>
</div>
<div v-if="item.params.hd_fix">
<el-divider> 高清修复 </el-divider>
<div class="info-line">
<div class="wrapper">
<label>重绘幅度</label>
<div class="item-value">
{{ item.params.hd_redraw_rate }}
</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大算法</label>
<div class="item-value">{{ item.params.hd_scale_alg }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大倍数</label>
<div class="item-value">{{ item.params.hd_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.hd_steps }}</div>
</div>
</div>
</div>
<div class="copy-params">
<el-button type="primary" round @click="copyParams(item)">画一张同款的</el-button>
</div>
</div>
</el-col>
</el-row>
</el-dialog>
<sd-task-view v-model="showTaskDialog" :data="item" @drawSame="copyParams" @close="showTaskDialog = false" />
</div>
</div>
</template>
@ -434,7 +331,7 @@ import { useSharedStore } from "@/store/sharedata";
import TaskList from "@/components/TaskList.vue";
import BackTop from "@/components/BackTop.vue";
import { showMessageError } from "@/utils/dialog";
import SdTaskView from "@/components/SdTaskView.vue";
const listBoxHeight = ref(0);
// const paramBoxHeight = ref(0)
const fullImgHeight = ref(window.innerHeight - 60);

View File

@ -3,7 +3,7 @@
<div class="inner custom-scroll">
<div class="header">
<h2 class="text-xl pt-4 pb-4">AI 绘画作品墙</h2>
<div class="settings">
<div class="settings pr-14">
<el-radio-group v-model="imgType" @change="changeImgType">
<el-radio value="mj" size="large">MidJourney</el-radio>
<el-radio value="sd" size="large">Stable Diffusion</el-radio>
@ -161,120 +161,7 @@
<!-- end of waterfall -->
</div>
<!-- 任务详情弹框 -->
<el-dialog v-model="showTaskDialog" title="绘画任务详情" :fullscreen="true">
<el-row :gutter="20">
<el-col :span="16">
<div class="img-container" :style="{ maxHeight: fullImgHeight + 'px' }">
<el-image :src="item['img_url']" fit="contain">
<template #placeholder>
<div class="image-slot">正在加载图片</div>
</template>
<template #error>
<div class="image-slot">
<el-icon>
<Picture />
</el-icon>
</div>
</template>
</el-image>
</div>
</el-col>
<el-col :span="8">
<div class="task-info">
<div class="info-line">
<el-divider> 正向提示词 </el-divider>
<div class="prompt">
<span>{{ item.prompt }}</span>
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.prompt">
<DocumentCopy />
</el-icon>
</div>
</div>
<div class="info-line">
<el-divider> 反向提示词 </el-divider>
<div class="prompt">
<span>{{ item.params.negative_prompt }}</span>
<el-icon class="copy-prompt-wall" :data-clipboard-text="item.params.negative_prompt">
<DocumentCopy />
</el-icon>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>采样方法</label>
<div class="item-value">{{ item.params.sampler }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>图片尺寸</label>
<div class="item-value">{{ item.params.width }} x {{ item.params.height }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.steps }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>引导系数</label>
<div class="item-value">{{ item.params.cfg_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>随机因子</label>
<div class="item-value">{{ item.params.seed }}</div>
</div>
</div>
<div v-if="item.params.hd_fix">
<el-divider> 高清修复 </el-divider>
<div class="info-line">
<div class="wrapper">
<label>重绘幅度</label>
<div class="item-value">{{ item.params.hd_redraw_rate }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大算法</label>
<div class="item-value">{{ item.params.hd_scale_alg }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>放大倍数</label>
<div class="item-value">{{ item.params.hd_scale }}</div>
</div>
</div>
<div class="info-line">
<div class="wrapper">
<label>迭代步数</label>
<div class="item-value">{{ item.params.hd_steps }}</div>
</div>
</div>
</div>
<div class="copy-params">
<el-button type="primary" round @click="drawSameSd(item)">画一张同款的</el-button>
</div>
</div>
</el-col>
</el-row>
</el-dialog>
<sd-task-view v-model="showTaskDialog" :data="item" @drawSame="drawSameSd" @close="showTaskDialog = false" />
</div>
</template>
@ -287,7 +174,7 @@ import { ElMessage } from "element-plus";
import Clipboard from "clipboard";
import { useRouter } from "vue-router";
import BackTop from "@/components/BackTop.vue";
import SdTaskView from "@/components/SdTaskView.vue";
const data = ref({
mj: [],
sd: [],
@ -298,7 +185,6 @@ const isOver = ref(false);
const imgType = ref("mj"); //
const listBoxHeight = window.innerHeight - 124;
const colWidth = ref(220);
const fullImgHeight = ref(window.innerHeight - 60);
const showTaskDialog = ref(false);
const item = ref({});

View File

@ -6,9 +6,7 @@
<el-menu mode="horizontal" :ellipsis="false">
<div class="menu-item">
<!-- <el-image :src="logo" class="logo" alt="Geek-AI" /> -->
<div class="logo-box">
<img src="@/assets/img/logo.png" alt="" />
</div>
<img :src="logo" class="logo" alt="" />
</div>
<div class="menu-item">
<span v-if="!license.de_copy">

View File

@ -5,12 +5,7 @@
<AccountTop>
<template #default>
<div class="wechatLog flex-center" v-if="wechatLoginURL !== ''">
<a
:href="wechatLoginURL"
@click="setRoute(router.currentRoute.value.path)"
>
<i class="iconfont icon-wechat"></i>使用微信登录
</a>
<a :href="wechatLoginURL" @click="setRoute(router.currentRoute.value.path)"> <i class="iconfont icon-wechat"></i>使用微信登录 </a>
</div>
</template>
</AccountTop>
@ -19,47 +14,26 @@
<el-form ref="ruleFormRef" :model="ruleForm" :rules="rules">
<el-form-item label="" prop="username">
<div class="form-title">账号</div>
<el-input
v-model="ruleForm.username"
size="large"
placeholder="请输入账号"
@keyup="handleKeyup"
/>
<el-input v-model="ruleForm.username" size="large" placeholder="请输入账号" @keyup="handleKeyup" />
</el-form-item>
<el-form-item label="" prop="password">
<div class="flex-between w100">
<div class="form-title">密码</div>
<div
class="form-forget text-color-primary"
@click="router.push('/resetpassword')"
>
忘记密码
</div>
<div class="form-forget text-color-primary" @click="router.push('/resetpassword')">忘记密码</div>
</div>
<el-input
size="large"
v-model="ruleForm.password"
placeholder="请输入密码"
show-password
autocomplete="off"
@keyup="handleKeyup"
/>
<el-input size="large" v-model="ruleForm.password" placeholder="请输入密码" show-password autocomplete="off" @keyup="handleKeyup" />
</el-form-item>
<el-form-item>
<el-button
class="login-btn"
size="large"
type="primary"
@click="login"
>登录</el-button
>
<el-button class="login-btn" size="large" type="primary" @click="login">登录</el-button>
</el-form-item>
</el-form>
</div>
</div>
</div>
<account-bg />
<captcha v-if="enableVerify" @success="doLogin" ref="captchaRef" />
</div>
</template>
@ -76,6 +50,7 @@ import { setRoute } from "@/store/system";
import { useSharedStore } from "@/store/sharedata";
import AccountTop from "@/components/AccountTop.vue";
import Captcha from "@/components/Captcha.vue";
const router = useRouter();
const title = ref("Geek-AI");
@ -87,16 +62,14 @@ const licenseConfig = ref({});
const wechatLoginURL = ref("");
const enableVerify = ref(false);
const captchaRef = ref(null);
//
const needVerify = ref(false);
const ruleFormRef = ref(null);
const ruleForm = reactive({
username: process.env.VUE_APP_USER,
password: process.env.VUE_APP_PASS
password: process.env.VUE_APP_PASS,
});
const rules = {
username: [{ required: true, trigger: "blur", message: "请输入账号" }],
password: [{ required: true, trigger: "blur", message: "请输入密码" }]
password: [{ required: true, trigger: "blur", message: "请输入密码" }],
};
onMounted(() => {
//
@ -147,7 +120,7 @@ const handleKeyup = (e) => {
const login = async function () {
await ruleFormRef.value.validate(async (valid) => {
if (valid) {
if (enableVerify.value && needVerify.value) {
if (enableVerify.value) {
captchaRef.value.loadCaptcha();
} else {
doLogin({});
@ -163,12 +136,11 @@ const doLogin = (verifyData) => {
password: password.value.trim(),
key: verifyData.key,
dots: verifyData.dots,
x: verifyData.x
x: verifyData.x,
})
.then((res) => {
setUserToken(res.data.token);
store.setIsLogin(true);
needVerify.value = false;
if (isMobile()) {
router.push("/mobile");
} else {
@ -177,7 +149,6 @@ const doLogin = (verifyData) => {
})
.catch((e) => {
showMessageError("登录失败," + e.message);
needVerify.value = true;
});
};
</script>

View File

@ -1,7 +1,7 @@
<template>
<div class="index container">
<h2 class="title">{{title}}</h2>
<van-notice-bar left-icon="info-o" :scrollable="true">{{slogan}}}</van-notice-bar>
<h2 class="title">{{ title }}</h2>
<van-notice-bar left-icon="info-o" :scrollable="true">{{ slogan }}}</van-notice-bar>
<div class="content">
<van-grid :column-num="3" :gutter="10" border>
@ -11,7 +11,6 @@
</template>
<template #text>
<div class="text">AI 对话</div>
</template>
</van-grid-item>
@ -35,32 +34,25 @@
</van-grid>
<div class="app-list">
<van-list
v-model:loading="loading"
:finished="true"
finished-text=""
@load="fetchApps"
>
<van-list v-model:loading="loading" :finished="true" finished-text="" @load="fetchApps">
<van-cell v-for="item in apps" :key="item.id">
<div>
<div class="item">
<div class="image">
<div class="image flex justify-center items-center">
<van-image :src="item.icon" />
</div>
<div class="info">
<div class="info-title">{{item.name}}</div>
<div class="info-text">{{item.hello_msg}}</div>
<div class="info-title">{{ item.name }}</div>
<div class="info-text">{{ item.hello_msg }}</div>
</div>
</div>
<div class="btn">
<div v-if="hasRole(item.key)">
<van-button size="small" type="success" @click="useRole(item.id)">使用</van-button>
<van-button size="small" type="danger" @click="updateRole(item,'remove')">移除</van-button>
<van-button size="small" type="danger" @click="updateRole(item, 'remove')">移除</van-button>
</div>
<van-button v-else size="small"
style="--el-color-primary:#009999"
@click="updateRole(item, 'add')">
<van-button v-else size="small" style="--el-color-primary: #009999" @click="updateRole(item, 'add')">
<van-icon name="add-o" />
<span>添加应用</span>
</van-button>
@ -74,91 +66,98 @@
</template>
<script setup>
import {onMounted, ref} from "vue";
import {useRouter} from "vue-router";
import {checkSession, getSystemInfo} from "@/store/cache";
import {httpGet, httpPost} from "@/utils/http";
import {arrayContains, removeArrayItem, showLoginDialog, substr} from "@/utils/libs";
import {showNotify} from "vant";
import {ElMessage} from "element-plus";
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { checkSession, getSystemInfo } from "@/store/cache";
import { httpGet, httpPost } from "@/utils/http";
import { arrayContains, removeArrayItem, showLoginDialog, substr } from "@/utils/libs";
import { showNotify } from "vant";
import { ElMessage } from "element-plus";
const title = ref(process.env.VUE_APP_TITLE)
const router = useRouter()
const isLogin = ref(false)
const apps = ref([])
const loading = ref(false)
const roles = ref([])
const slogan = ref('你有多大想象力AI就有多大创造力')
const title = ref(process.env.VUE_APP_TITLE);
const router = useRouter();
const isLogin = ref(false);
const apps = ref([]);
const loading = ref(false);
const roles = ref([]);
const slogan = ref("你有多大想象力AI就有多大创造力");
onMounted(() => {
getSystemInfo().then(res => {
title.value = res.data.title
if (res.data.slogan) {
slogan.value = res.data.slogan
}
}).catch(e => {
ElMessage.error("获取系统配置失败:" + e.message)
})
getSystemInfo()
.then((res) => {
title.value = res.data.title;
if (res.data.slogan) {
slogan.value = res.data.slogan;
}
})
.catch((e) => {
ElMessage.error("获取系统配置失败:" + e.message);
});
checkSession().then((user) => {
isLogin.value = true
roles.value = user.chat_roles
}).catch(() => {
})
fetchApps()
})
checkSession()
.then((user) => {
isLogin.value = true;
roles.value = user.chat_roles;
})
.catch(() => {});
fetchApps();
});
const fetchApps = () => {
httpGet("/api/app/list").then((res) => {
const items = res.data
// hello message
for (let i = 0; i < items.length; i++) {
items[i].intro = substr(items[i].hello_msg, 80)
}
apps.value = items
}).catch(e => {
showNotify({type:"danger", message:"获取应用失败:" + e.message})
})
}
httpGet("/api/app/list")
.then((res) => {
const items = res.data;
// hello message
for (let i = 0; i < items.length; i++) {
items[i].intro = substr(items[i].hello_msg, 80);
}
apps.value = items;
})
.catch((e) => {
showNotify({ type: "danger", message: "获取应用失败:" + e.message });
});
};
const updateRole = (row, opt) => {
if (!isLogin.value) {
return showLoginDialog(router)
return showLoginDialog(router);
}
const title = ref("")
const title = ref("");
if (opt === "add") {
title.value = "添加应用"
const exists = arrayContains(roles.value, row.key)
title.value = "添加应用";
const exists = arrayContains(roles.value, row.key);
if (exists) {
return
return;
}
roles.value.push(row.key)
roles.value.push(row.key);
} else {
title.value = "移除应用"
const exists = arrayContains(roles.value, row.key)
title.value = "移除应用";
const exists = arrayContains(roles.value, row.key);
if (!exists) {
return
return;
}
roles.value = removeArrayItem(roles.value, row.key)
roles.value = removeArrayItem(roles.value, row.key);
}
httpPost("/api/role/update", {keys: roles.value}).then(() => {
ElMessage.success({message: title.value + "成功!", duration: 1000})
}).catch(e => {
ElMessage.error(title.value + "失败:" + e.message)
})
}
httpPost("/api/role/update", { keys: roles.value })
.then(() => {
ElMessage.success({ message: title.value + "成功!", duration: 1000 });
})
.catch((e) => {
ElMessage.error(title.value + "失败:" + e.message);
});
};
const hasRole = (roleKey) => {
return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2)
}
return arrayContains(roles.value, roleKey, (v1, v2) => v1 === v2);
};
const useRole = (roleId) => {
if (!isLogin.value) {
return showLoginDialog(router)
return showLoginDialog(router);
}
router.push(`/mobile/chat/session?role_id=${roleId}`)
}
router.push(`/mobile/chat/session?role_id=${roleId}`);
};
</script>
<style scoped lang="stylus">

View File

@ -0,0 +1,43 @@
<template>
<div class="login flex w-full flex-col place-content-center h-lvh">
<el-image src="/images/logo.png" class="w-1/2 mx-auto logo" />
<div class="title text-center text-3xl font-bold mt-8">{{ title }}</div>
<login-dialog @success="loginSuccess" />
</div>
</template>
<script setup>
import LoginDialog from "@/components/LoginDialog.vue";
import { getSystemInfo } from "@/store/cache";
import { useRouter } from "vue-router";
import { ref, onMounted } from "vue";
const router = useRouter();
const title = ref("登录");
const loginSuccess = () => {
router.back();
};
onMounted(() => {
getSystemInfo().then((res) => {
title.value = res.data.title;
});
});
</script>
<style scoped lang="stylus">
.login {
background: var(--theme-bg);
transition: all 0.3s ease;
.logo {
background: #ffffff;
border-radius: 50%;
}
.title {
color: var(--text-theme-color);
}
}
</style>