2.5版本 增加dall-e 优化mj 对接mj-plus

This commit is contained in:
小易
2024-02-04 18:51:37 +08:00
parent 822ff0a51d
commit 2ca3c164a0
57 changed files with 12239 additions and 7693 deletions

View File

@@ -0,0 +1,377 @@
<script lang="ts" setup>
import {
onMounted,
ref,
computed,
onUnmounted,
getCurrentInstance,
watch,
nextTick,
} from 'vue';
import { throttle } from '@/utils/functions/throttle';
import { SvgIcon } from '@/components/common';
import { copyText } from '@/utils/format';
import { useMessage, NPopover } from 'naive-ui';
import { useAuthStore } from '@/store';
import { useRouter } from 'vue-router';
const authStore = useAuthStore();
interface Props {
dataList: FileItem[];
scaleWidth?: number;
isDrawLike?: boolean;
usePropmpt?: boolean;
copyPropmpt?: boolean;
gap?: number;
}
interface FileItem {
id: number;
drawUrl: string;
fullPrompt?: string;
drawRatio: string;
}
interface Emit {
(ev: 'loadMore'): void;
(ev: 'usePropmptDraw', prompt: string): void;
}
const props = withDefaults(defineProps<Props>(), {
gap: 5,
});
const emit = defineEmits<Emit>();
const $viewerApi =
getCurrentInstance()?.appContext.config.globalProperties.$viewerApi;
const ms = useMessage();
const boxRefs = ref<any>({});
const otherInfoContainerHeight = ref(0);
const realWidth = ref(160);
const realColumn = ref(0);
const loadComplete = ref<number[]>([]);
const wapperRef = ref<HTMLDivElement | null>(null);
const wapperHeigth = ref(0);
const isLogin = computed(() => authStore.isLogin);
const width = computed(() => {
return props.scaleWidth
? Number(props.scaleWidth) * 2 + props.gap + 150
: 150;
});
const router = useRouter();
/* 拿到图片高度 对定位top和right 新的一轮去插入最小值的那一列 贪心算法即可 */
function compilerContainer() {
calcHeight();
compilerColumn();
const columns = realColumn.value;
const itemWidth = realWidth.value;
const cacheHeight = <any>[];
props.dataList.forEach((item, index) => {
const drawRatio = item.drawRatio; // 假设 drawRatio 是 "1632x2912"
const dimensions = drawRatio.split('x'); // 使用 'x' 分割字符串
const width = parseInt(dimensions[0], 10); // 宽度,转换为数字
const height = parseInt(dimensions[1], 10); // 高度,转换为数字
const bi = itemWidth / width;
const boxheight = height * bi + props.gap + otherInfoContainerHeight.value;
const currentBox = boxRefs.value[item.id];
if (cacheHeight.length < columns) {
currentBox.style.top = '0px';
currentBox.style.left = `${(itemWidth + props.gap) * index}px`;
cacheHeight.push(boxheight);
} else {
const minHeight = Math.min.apply(null, cacheHeight);
const minIndex = cacheHeight.findIndex((t: number) => t === minHeight);
currentBox.style.top = `${minHeight + 0}px`;
currentBox.style.left = `${minIndex * (realWidth.value + props.gap)}px`;
cacheHeight[minIndex] += boxheight;
}
});
wapperHeigth.value = Math.max(...cacheHeight) + 100;
}
function setItemRefs(el: HTMLDivElement, item: FileItem) {
if (el && item) {
boxRefs.value[item.id] = el;
}
}
/* 通过额外展示的信息计算有没有除了图片意外额外的高度 eg 图片100px 额外显示其他信息30px cacheHeight的高度在图片的基础上需要+30 */
function calcHeight() {
const { showName = 0, showOther = 0 } = {};
otherInfoContainerHeight.value =
[showName, showOther].filter((t) => t).length * 15;
}
watch(
() => props.scaleWidth,
(val) => {
handleResizeThrottled();
}
);
watch(
() => props.dataList,
(val) => {
if (!val) return;
nextTick(() => {
handleResizeThrottled();
});
},
{ immediate: true }
);
/* 计算放多少列比较合理,并计算最终单个图片的宽 */
function compilerColumn() {
if (!wapperRef.value) return;
const containerWidth = wapperRef.value.clientWidth;
/* 计算按目前宽度最多可以是几列 */
realColumn.value = Math.floor(containerWidth / width.value);
const surplus = containerWidth - realColumn.value * width.value; // 剩下的多余空间
/* 计算如果给了左右间距那么作业间距需要占多少宽度 */
const positionWith = (realColumn.value - 1) * props.gap; // 设置的right 需要padding的值
/* 总宽度减去right的宽度如果是负数考虑要不要cloumn-1 那么图片真实宽度就会比传入的宽度大 */
if (surplus - positionWith < 0) {
realColumn.value -= 1;
}
/* 图片宽度*列 + right的间距 不管大于小于总宽 多的或者少的那部分都平分给列容器 保证总宽是100% */
realWidth.value = Math.floor(
(containerWidth - positionWith) / realColumn.value
);
}
function imgLoadSuccess(e: any, item: FileItem) {
loadComplete.value.push(item.id);
}
function imgLoadError(e: any, item: FileItem) {
console.error('Image failed to load:', item);
loadComplete.value.push(item.id);
}
function handleCopy(item: any) {
if (!isLogin.value) {
return authStore.setLoginDialog(true);
}
const { prompt } = item;
copyText({ text: prompt });
ms.success('复制prompt成功');
}
function drawLike(item: any) {
router.push(`/midjourney?mjId=${item.id}`);
}
function usePropmptDraw(item: FileItem) {
const { fullPrompt } = item;
emit('usePropmptDraw', fullPrompt);
}
function handlePreview(item: any) {
const { drawUrl } = item;
$viewerApi({ options: {}, images: [drawUrl] });
}
const realHeight = computed(() => (item) => {
const ratio = item.drawRatio.split('x');
const originalWidth = Number(ratio[0]);
const originalHeight = Number(ratio[1]);
return originalHeight / (originalWidth / realWidth.value);
});
const handleResizeThrottled = throttle(function (this: any) {
compilerContainer();
}, 200);
onMounted(async () => {
window.addEventListener('resize', handleResizeThrottled);
const container: any = document.getElementById('footer');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
emit('loadMore');
}
});
});
observer.observe(container);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResizeThrottled);
});
</script>
<template>
<div class="min-h-full overflow-hidden flex flex-col">
<div class="flex-1 min-h-full p-4 relative">
<div
id="wapper"
ref="wapperRef"
class="wapper"
:style="{ height: `${wapperHeigth}px` }"
>
<div
v-for="(item, index) in dataList"
:id="item.id.toString()"
:key="index"
:ref="(el) => setItemRefs(el, item)"
class="wapper-item"
:style="{ width: `${realWidth}px` }"
>
<transition name="img" :css="true">
<img
:id="item.id.toString()"
class="item-file rounded-sm"
:style="{
width: `${realWidth}px`,
height: `${realHeight(item)}px`,
}"
:src="item.drawUrl"
loading="lazy"
@load="imgLoadSuccess($event, item)"
@error="imgLoadError($event, item)"
@click="handlePreview(item)"
/>
</transition>
<div class="menu p-2 text-[#cbd5e1]">
<div class="prompt">
{{ item.fullPrompt }}
</div>
<div class="flex justify-end items-end space-x-2">
<n-popover trigger="hover" v-if="isDrawLike">
<template #trigger>
<button
class="flex h-5 w-8 items-center justify-center rounded border transition hover:bg-[#666161] dark:border-neutral-700 dark:hover:bg-[#33373c]"
@click.stop="drawLike(item)"
>
<span class="text-sm dark:text-slate-400">
<SvgIcon
icon="fluent:draw-image-24-regular"
class="text-sm"
/>
</span>
</button>
</template>
<span>画同款</span>
</n-popover>
<n-popover trigger="hover" v-if="usePropmpt">
<template #trigger>
<button
class="flex h-5 w-8 items-center justify-center rounded border transition hover:bg-[#666161] dark:border-neutral-700 dark:hover:bg-[#33373c]"
@click.stop="usePropmptDraw(item)"
>
<span class="text-sm dark:text-slate-400">
<SvgIcon
icon="fluent:draw-image-24-regular"
class="text-sm"
/>
</span>
</button>
</template>
<span>使用当前画同款</span>
</n-popover>
<n-popover trigger="hover" v-if="copyPropmpt">
<template #trigger>
<button
class="flex h-5 w-8 items-center justify-center rounded border transition hover:bg-[#666161] dark:border-neutral-700 dark:hover:bg-[#33373c]"
@click.stop="handleCopy(item)"
>
<span class="text-sm dark:text-slate-400">
<SvgIcon icon="tabler:copy" class="text-sm" />
</span>
</button>
</template>
<span>复制提示词</span>
</n-popover>
</div>
</div>
<div
class="item-loading"
v-if="!loadComplete.includes(item.id)"
:style="{
width: `${realWidth}px`,
height: `${realHeight(item)}px`,
}"
></div>
</div>
<div id="footer" class="w-full absolute bottom-[350px]" />
</div>
</div>
</div>
</template>
<style lang="less">
.wapper {
width: 100%;
position: relative;
height: 100%;
padding-bottom: 20px;
&-item {
z-index: 10;
overflow: hidden;
position: absolute;
transition: all 0.5s;
cursor: pointer;
&:hover {
.menu {
transition: transform 0.3s ease-in-out;
transform: translateY(-10px);
}
img {
transform: scale(1.1);
}
}
.menu {
position: absolute;
bottom: 0;
width: 94%;
left: 3%;
max-height: 70%;
height: 100px;
transform: translateY(100%);
background-color: #090b15;
opacity: 0.8;
transition: all 0.1s cubic-bezier(0.68, -0.55, 0.265, 1.55);
border-radius: 10px;
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: space-between;
.prompt {
height: 50px;
overflow: hidden;
}
}
img {
user-select: none;
cursor: pointer;
transition: all 0.6s cubic-bezier(0.19, 1, 0.22, 1);
border-radius: 6px;
}
.item-loading {
background: url(@/assets/img-bg.png) no-repeat center center;
filter: blur(20px);
position: absolute;
top: 0;
}
}
}
.img-enter-active,
.img-leave-active {
transition: transform 0.3s;
}
.img-enter,
.img-leave-to {
transform: scale(0.6);
opacity: 0;
}
</style>

View File

@@ -317,7 +317,7 @@ onUnmounted(() => {
}
.item-loading {
background: url(../../assets/img-bg.png) no-repeat center center;
background: url(@/assets/img-bg.png) no-repeat center center;
filter: blur(20px);
position: absolute;
top: 0;