refactor(web-antd): 修正 Tinyflow 组件中的导入路径

- 将 './ui/typing' 修改为 './ui/typeing'(可能是原代码中的拼写错误)
- 将 './ui/typing' 修改为 './ui/index',以符合常见的模块入口文件结构
pull/145/head
gjd 2025-06-16 13:16:07 +08:00
parent 0f701dd49b
commit 31a98ba9f8
27 changed files with 700 additions and 1592 deletions

View File

@ -1,9 +1,9 @@
<script setup lang="ts">
import type { Item } from './ui/typing';
import type { Item } from './ui/typeing';
import { onMounted, onUnmounted, ref } from 'vue';
import { Tinyflow as TinyflowNative } from './ui/typing';
import { Tinyflow as TinyflowNative } from './ui/index';
import './ui/index.css';

View File

@ -1,11 +1,55 @@
import { Edge, Node as Node_2, useSvelteFlow, Viewport } from '@xyflow/svelte';
export declare type Item = {
children?: Item[];
label: string;
value: number | string;
};
export type Position = {
x: number;
y: number;
};
export type Viewport = {
x: number;
y: number;
zoom: number;
};
export type Node = {
data?: Record<string, any>;
draggable?: boolean;
height?: number;
id: string;
position: Position;
selected?: boolean;
type?: string;
width?: number;
};
export type Edge = {
animated?: boolean;
id: string;
label?: string;
source: string;
target: string;
type?: string;
};
export type TinyflowData = Partial<{
edges: Edge[];
nodes: Node[];
viewport: Viewport;
}>;
export declare type TinyflowOptions = {
data?: TinyflowData;
element: Element | string;
provider?: {
internal?: () => Item[] | Promise<Item[]>;
knowledge?: () => Item[] | Promise<Item[]>;
llm?: () => Item[] | Promise<Item[]>;
};
};
export declare class Tinyflow {
private _init;
private _setOptions;
@ -16,25 +60,11 @@ export declare class Tinyflow {
destroy(): void;
getData(): {
edges: Edge[];
nodes: Node_2[];
nodes: Node[];
viewport: Viewport;
};
getOptions(): TinyflowOptions;
setData(data: TinyflowData): void;
}
export declare type TinyflowData = Partial<
ReturnType<ReturnType<typeof useSvelteFlow>['toObject']>
>;
export declare type TinyflowOptions = {
data?: TinyflowData;
element: Element | string;
provider?: {
internal?: () => Item[] | Promise<Item[]>;
knowledge?: () => Item[] | Promise<Item[]>;
llm?: () => Item[] | Promise<Item[]>;
};
};
export {};

View File

@ -0,0 +1,98 @@
const download0 = (data: Blob, fileName: string, mineType: string) => {
// 创建 blob
const blob = new Blob([data], { type: mineType });
// 创建 href 超链接,点击进行下载
window.URL = window.URL || window.webkitURL;
const href = URL.createObjectURL(blob);
const downA = document.createElement('a');
downA.href = href;
downA.download = fileName;
downA.click();
// 销毁超连接
window.URL.revokeObjectURL(href);
};
export const download = {
// 下载 Excel 方法
excel: (data: Blob, fileName: string) => {
download0(data, fileName, 'application/vnd.ms-excel');
},
// 下载 Word 方法
word: (data: Blob, fileName: string) => {
download0(data, fileName, 'application/msword');
},
// 下载 Zip 方法
zip: (data: Blob, fileName: string) => {
download0(data, fileName, 'application/zip');
},
// 下载 Html 方法
html: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/html');
},
// 下载 Markdown 方法
markdown: (data: Blob, fileName: string) => {
download0(data, fileName, 'text/markdown');
},
// 下载 Json 方法
json: (data: Blob, fileName: string) => {
download0(data, fileName, 'application/json');
},
// 下载图片(允许跨域)
image: ({
url,
canvasWidth,
canvasHeight,
drawWithImageSize = true,
}: {
canvasHeight?: number; // 指定画布高度
canvasWidth?: number; // 指定画布宽度
drawWithImageSize?: boolean; // 将图片绘制在画布上时带上图片的宽高值, 默认是要带上的
url: string;
}) => {
const image = new Image();
// image.setAttribute('crossOrigin', 'anonymous')
image.src = url;
image.addEventListener('load', () => {
const canvas = document.createElement('canvas');
canvas.width = canvasWidth || image.width;
canvas.height = canvasHeight || image.height;
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx?.clearRect(0, 0, canvas.width, canvas.height);
if (drawWithImageSize) {
ctx.drawImage(image, 0, 0, image.width, image.height);
} else {
ctx.drawImage(image, 0, 0);
}
const url = canvas.toDataURL('image/png');
const a = document.createElement('a');
a.href = url;
a.download = 'image.png';
a.click();
});
},
base64ToFile: (base64: any, fileName: string) => {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
const data = base64.split(',');
// 利用正则表达式 从前缀中获取图片的类型信息image/png、image/jpeg、image/webp等
const type = data[0].match(/:(.*?);/)[1];
// 从图片的类型信息中 获取具体的文件格式后缀png、jpeg、webp
const suffix = type.split('/')[1];
// 使用atob()对base64数据进行解码 结果是一个文件数据流 以字符串的格式输出
const bstr = window.atob(data[1]);
// 获取解码结果字符串的长度
let n = bstr.length;
// 根据解码结果字符串的长度创建一个等长的整形数字数组
// 但在创建时 所有元素初始值都为 0
const u8arr = new Uint8Array(n);
// 将整形数组的每个元素填充为解码结果字符串对应位置字符的UTF-16 编码单元
while (n--) {
// charCodeAt():获取给定索引处字符对应的 UTF-16 代码单元
u8arr[n] = bstr.charCodeAt(n);
}
// 将File文件对象返回给方法的调用者
return new File([u8arr], `${fileName}.${suffix}`, {
type,
});
},
};

View File

@ -303,23 +303,26 @@ onMounted(async () => {
</script>
<template>
<Layout.Sider width="260px" class="conversation-container h-full">
<Layout.Sider
width="260px"
class="conversation-container relative flex h-full flex-col justify-between overflow-hidden bg-[hsl(var(--primary-foreground))!important] p-[10px_10px_0]"
>
<Drawer />
<!-- 左顶部对话 -->
<div class="flex h-full" style="flex-direction: column">
<div class="flex h-full flex-col">
<Button
class="w-1/1 btn-new-conversation"
class="btn-new-conversation h-[38px] w-full"
type="primary"
@click="createConversation"
>
<IconifyIcon icon="ep:plus" class="mr-[5px]" />
新建对话
</Button>
<!-- 左顶部搜索对话 -->
<Input
v-model:value="searchName"
size="large"
class="search-input mt-[10px]"
class="search-input mt-[20px]"
placeholder="搜索历史记录"
@keyup="searchConversation"
>
@ -329,50 +332,57 @@ onMounted(async () => {
</Input>
<!-- 左中间对话列表 -->
<div class="conversation-list">
<div class="conversation-list mt-[10px] flex-1 overflow-auto">
<!-- 情况一加载中 -->
<Empty v-if="loading" description="." v-loading="loading" />
<!-- 情况二按照 group 分组展示聊天会话 list 列表 -->
<!-- 情况二按照 group 分组 -->
<div
v-for="conversationKey in Object.keys(conversationMap)"
:key="conversationKey"
class=""
>
<div
class="conversation-item classify-title"
v-if="conversationMap[conversationKey].length > 0"
class="conversation-item classify-title pt-[10px]"
>
<b class="mx-1">
<b class="mx-[4px]">
{{ conversationKey }}
</b>
</div>
<div
class="conversation-item"
v-for="conversation in conversationMap[conversationKey]"
:key="conversation.id"
@click="handleConversationClick(conversation.id)"
@mouseover="hoverConversationId = conversation.id"
@mouseout="hoverConversationId = null"
class="conversation-item mt-[5px]"
>
<div
:class="
conversation.id === activeConversationId
? 'conversation active'
: 'conversation'
"
class="conversation flex cursor-pointer flex-row items-center justify-between rounded-[5px] px-[5px] leading-[30px]"
:class="[
conversation.id === activeConversationId ? 'bg-[#e6e6e6]' : '',
]"
>
<div class="title-wrapper">
<div class="title-wrapper flex items-center">
<img
class="avatar"
class="avatar h-[25px] w-[25px] rounded-[5px]"
:src="conversation.roleAvatar ?? '/static/gpt.svg'"
/>
<span class="title">{{ conversation.title }}</span>
<span
class="title text-black/77 max-w-[150px] overflow-hidden text-ellipsis whitespace-nowrap px-[10px] py-[2px] text-[14px] font-normal"
>
{{ conversation.title }}
</span>
</div>
<div
class="button-wrapper"
v-show="hoverConversationId === conversation.id"
class="button-wrapper relative right-[2px] flex items-center text-[#606266]"
>
<Button
class="btn"
class="btn mr-0 px-[5px]"
type="link"
@click.stop="handleTop(conversation)"
>
@ -386,14 +396,14 @@ onMounted(async () => {
></span>
</Button>
<Button
class="btn"
class="btn mr-0 px-[5px]"
type="link"
@click.stop="updateConversationTitle(conversation)"
>
<IconifyIcon icon="ep:edit" />
</Button>
<Button
class="btn"
class="btn mr-0 px-[5px]"
type="link"
@click.stop="deleteChatConversation(conversation)"
>
@ -404,143 +414,29 @@ onMounted(async () => {
</div>
</div>
</div>
<!-- 底部占位 -->
<div class="w-100% h-[50px]"></div>
<!-- 底部占位 -->
<div class="h-[50px] w-full"></div>
</div>
<!-- 左底部工具栏 -->
<div class="tool-box">
<div @click="handleRoleRepository">
<div
class="tool-box absolute bottom-0 left-0 right-0 flex items-center justify-between bg-[#f4f4f4] px-[20px] leading-[35px] text-[var(--el-text-color)] shadow-[0_0_1px_1px_rgba(228,228,228,0.8)]"
>
<div
class="flex cursor-pointer items-center text-[#606266]"
@click="handleRoleRepository"
>
<IconifyIcon icon="ep:user" />
<span>角色仓库</span>
<span class="ml-[5px]">角色仓库</span>
</div>
<div @click="handleClearConversation">
<div
class="flex cursor-pointer items-center text-[#606266]"
@click="handleClearConversation"
>
<IconifyIcon icon="ep:delete" />
<span>清空未置顶对话</span>
<span class="ml-[5px]">清空未置顶对话</span>
</div>
</div>
</Layout.Sider>
</template>
<style scoped lang="scss">
.conversation-container {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px 10px 0;
overflow: hidden;
background-color: hsl(var(--primary-foreground));
.btn-new-conversation {
width: 100%;
height: 38px;
}
.search-input {
margin-top: 20px;
}
.conversation-list {
height: 100%;
overflow: auto;
.classify-title {
padding-top: 10px;
}
.conversation-item {
margin-top: 5px;
}
.conversation {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 5px;
line-height: 30px;
cursor: pointer;
border-radius: 5px;
&.active {
background-color: #e6e6e6;
.button {
display: inline-block;
}
}
.title-wrapper {
display: flex;
flex-direction: row;
align-items: center;
}
.title {
max-width: 150px;
padding: 2px 10px;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
font-weight: 400;
color: rgb(0 0 0 / 77%);
white-space: nowrap;
}
.avatar {
display: flex;
flex-direction: row;
justify-items: center;
width: 25px;
height: 25px;
border-radius: 5px;
}
//
.button-wrapper {
right: 2px;
display: flex;
flex-direction: row;
place-items: center center;
color: #606266;
.btn {
padding: 0 5px 0 0;
margin: 0;
}
}
}
}
//
.tool-box {
position: absolute;
right: 0;
bottom: 0;
left: 0;
display: flex;
align-items: center;
justify-content: space-between;
//width: 100%;
padding: 0 20px;
line-height: 35px;
color: var(--el-text-color);
background-color: #f4f4f4;
box-shadow: 0 0 1px 1px rgb(228 228 228 / 80%);
> div {
display: flex;
align-items: center;
padding: 0;
margin: 0;
color: #606266;
cursor: pointer;
> span {
margin-left: 5px;
}
}
}
}
</style>

View File

@ -113,66 +113,92 @@ onMounted(async () => {
</script>
<template>
<div ref="messageContainer" class="relative h-full overflow-y-auto">
<div class="chat-list" v-for="(item, index) in list" :key="index">
<!-- 靠左 messagesystemassistant 类型 -->
<div class="left-message message-item" v-if="item.type !== 'user'">
<div
v-for="(item, index) in list"
:key="index"
class="mt-[50px] flex flex-col overflow-y-hidden px-[20px]"
>
<!-- 左侧消息systemassistant -->
<div v-if="item.type !== 'user'" class="flex flex-row">
<div class="avatar">
<Avatar :src="roleAvatar" />
</div>
<div class="message">
<div>
<div class="time">{{ formatDate(item.createTime) }}</div>
<div class="mx-[15px] flex flex-col text-left">
<div class="text-left leading-[30px]">
{{ formatDate(item.createTime) }}
</div>
<div class="left-text-container">
<MarkdownView class="left-text" :content="item.content" />
<div
class="relative flex flex-col break-words rounded-[10px] bg-[#e4e4e4cc] p-[10px] pb-[5px] pt-[10px] shadow-[0_0_0_1px_rgba(228,228,228,0.8)]"
>
<MarkdownView
class="text-[0.95rem] text-[#393939]"
:content="item.content"
/>
<MessageKnowledge v-if="item.segments" :segments="item.segments" />
</div>
<div class="left-btns">
<div class="mt-[8px] flex flex-row">
<Button
class="btn-cus"
class="flex items-center bg-transparent px-[5px] hover:bg-[#f6f6f6]"
type="text"
@click="copyContent(item.content)"
>
<img class="btn-image" src="/static/copy.svg" />
<img class="h-[20px]" src="/static/copy.svg" />
</Button>
<Button
v-if="item.id > 0"
class="btn-cus"
class="flex items-center bg-transparent px-[5px] hover:bg-[#f6f6f6]"
type="text"
@click="onDelete(item.id)"
>
<img class="btn-image h-[17px]" src="/static/delete.svg" />
<img class="h-[17px]" src="/static/delete.svg" />
</Button>
</div>
</div>
</div>
<!-- 靠右 messageuser 类型 -->
<div class="right-message message-item" v-if="item.type === 'user'">
<!-- 右侧消息user -->
<div v-else class="flex flex-row-reverse justify-start">
<div class="avatar">
<Avatar :src="userAvatar" />
</div>
<div class="message">
<div>
<div class="time">{{ formatDate(item.createTime) }}</div>
<div class="mx-[15px] flex flex-col text-left">
<div class="text-left leading-[30px]">
{{ formatDate(item.createTime) }}
</div>
<div class="right-text-container">
<div class="right-text">{{ item.content }}</div>
<div class="flex flex-row-reverse">
<div
class="inline w-auto whitespace-pre-wrap break-words rounded-[10px] bg-[#267fff] p-[10px] text-[0.95rem] text-white shadow-[0_0_0_1px_#267fff]"
>
{{ item.content }}
</div>
</div>
<div class="right-btns">
<div class="mt-[8px] flex flex-row-reverse">
<Button
class="btn-cus"
class="flex items-center bg-transparent px-[5px] hover:bg-[#f6f6f6]"
type="text"
@click="copyContent(item.content)"
>
<img class="btn-image" src="/static/copy.svg" />
<img class="h-[20px]" src="/static/copy.svg" />
</Button>
<Button class="btn-cus" type="text" @click="onDelete(item.id)">
<img class="btn-image h-[17px]" src="/static/delete.svg" />
<Button
class="flex items-center bg-transparent px-[5px] hover:bg-[#f6f6f6]"
type="text"
@click="onDelete(item.id)"
>
<img class="h-[17px]" src="/static/delete.svg" />
</Button>
<Button class="btn-cus" type="text" @click="onRefresh(item)">
<Button
class="flex items-center bg-transparent px-[5px] hover:bg-[#f6f6f6]"
type="text"
@click="onRefresh(item)"
>
<span class="icon-[ant-design--redo-outlined]"></span>
</Button>
<Button class="btn-cus" type="text" @click="onEdit(item)">
<Button
class="flex items-center bg-transparent px-[5px] hover:bg-[#f6f6f6]"
type="text"
@click="onEdit(item)"
>
<span class="icon-[ant-design--form-outlined]"></span>
</Button>
</div>
@ -180,117 +206,15 @@ onMounted(async () => {
</div>
</div>
</div>
<!-- 回到底部 -->
<div v-if="isScrolling" class="to-bottom" @click="handleGoBottom">
<!-- 回到底部按钮 -->
<div
v-if="isScrolling"
class="absolute bottom-0 right-1/2 z-[1000]"
@click="handleGoBottom"
>
<Button shape="circle">
<span class="icon-[ant-design--down-outlined]"></span>
</Button>
</div>
</template>
<style scoped lang="scss">
//
.chat-list {
display: flex;
flex-direction: column;
padding: 0 20px;
overflow-y: hidden;
.message-item {
margin-top: 50px;
}
.left-message {
display: flex;
flex-direction: row;
}
.right-message {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
}
.message {
display: flex;
flex-direction: column;
margin: 0 15px;
text-align: left;
.time {
line-height: 30px;
text-align: left;
}
.left-text-container {
position: relative;
display: flex;
flex-direction: column;
padding: 10px 10px 5px;
overflow-wrap: break-word;
background-color: rgb(228 228 228 / 80%);
border-radius: 10px;
box-shadow: 0 0 0 1px rgb(228 228 228 / 80%);
.left-text {
font-size: 0.95rem;
color: #393939;
}
}
.right-text-container {
display: flex;
flex-direction: row-reverse;
.right-text {
display: inline;
width: auto;
padding: 10px;
font-size: 0.95rem;
color: #fff;
overflow-wrap: break-word;
white-space: pre-wrap;
background-color: #267fff;
border-radius: 10px;
box-shadow: 0 0 0 1px #267fff;
}
}
.left-btns {
display: flex;
flex-direction: row;
margin-top: 8px;
}
.right-btns {
display: flex;
flex-direction: row-reverse;
margin-top: 8px;
}
}
//
.btn-cus {
display: flex;
align-items: center;
padding: 0 5px;
background-color: transparent;
.btn-image {
height: 20px;
}
}
.btn-cus:hover {
cursor: pointer;
background-color: #f6f6f6;
}
}
//
.to-bottom {
position: absolute;
right: 50%;
bottom: 0;
z-index: 1000;
}
</style>

View File

@ -16,16 +16,21 @@ const handlerPromptClick = async (prompt: any) => {
};
</script>
<template>
<div class="chat-empty">
<!-- title -->
<div class="center-container">
<div class="title">芋道 AI</div>
<div class="role-list">
<div class="relative flex h-full w-full flex-row justify-center">
<!-- center-container -->
<div class="flex flex-col justify-center">
<!-- title -->
<div class="text-center text-[28px] font-bold">芋道 AI</div>
<!-- role-list -->
<div
class="mt-[20px] flex w-[460px] flex-wrap items-center justify-center"
>
<div
class="role-item"
v-for="prompt in promptList"
:key="prompt.prompt"
@click="handlerPromptClick(prompt)"
class="m-[10px] flex w-[180px] cursor-pointer justify-center rounded-[10px] border border-[#e4e4e4] leading-[50px] hover:bg-[rgba(243,243,243,0.73)]"
>
{{ prompt.prompt }}
</div>
@ -33,49 +38,3 @@ const handlerPromptClick = async (prompt: any) => {
</div>
</div>
</template>
<style scoped lang="scss">
.chat-empty {
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
height: 100%;
.center-container {
display: flex;
flex-direction: column;
justify-content: center;
.title {
font-size: 28px;
font-weight: bold;
text-align: center;
}
.role-list {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
width: 460px;
margin-top: 20px;
.role-item {
display: flex;
justify-content: center;
width: 180px;
margin: 10px;
line-height: 50px;
cursor: pointer;
border: 1px solid #e4e4e4;
border-radius: 10px;
}
.role-item:hover {
background-color: rgb(243 243 243 / 73%);
}
}
}
}
</style>

View File

@ -3,13 +3,7 @@ import { Skeleton } from 'ant-design-vue';
</script>
<template>
<div class="message-loading">
<div class="p-[30px]">
<Skeleton active />
</div>
</template>
<style scoped lang="scss">
.message-loading {
padding: 30px;
}
</style>

View File

@ -10,39 +10,14 @@ const handlerNewChat = () => {
</script>
<template>
<div class="new-chat">
<div class="box-center">
<div class="tip">点击下方按钮开始你的对话吧</div>
<div class="btns">
<div class="flex h-full w-full flex-row justify-center">
<div class="flex flex-col justify-center">
<div class="text-center text-[14px] text-[#858585]">
点击下方按钮开始你的对话吧
</div>
<div class="mt-[20px] flex flex-row justify-center">
<Button type="primary" round @click="handlerNewChat"></Button>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.new-chat {
display: flex;
flex-direction: row;
justify-content: center;
width: 100%;
height: 100%;
.box-center {
display: flex;
flex-direction: column;
justify-content: center;
.tip {
font-size: 14px;
color: #858585;
}
.btns {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 20px;
}
}
}
</style>

View File

@ -25,8 +25,12 @@ const handleCategoryClick = async (category: string) => {
</script>
<template>
<div class="category-list">
<div class="category" v-for="category in categoryList" :key="category">
<div class="flex flex-wrap items-center">
<div
class="mr-[10px] flex flex-row"
v-for="category in categoryList"
:key="category"
>
<Button
size="small"
shape="round"
@ -38,17 +42,3 @@ const handleCategoryClick = async (category: string) => {
</div>
</div>
</template>
<style scoped lang="scss">
.category-list {
display: flex;
flex-flow: row wrap;
align-items: center;
.category {
display: flex;
flex-direction: row;
margin-right: 10px;
}
}
</style>

View File

@ -60,10 +60,18 @@ const handleTabsScroll = async () => {
</script>
<template>
<div class="card-list" ref="tabsRef" @scroll="handleTabsScroll">
<div class="card-item" v-for="role in roleList" :key="role.id">
<div
class="relative flex h-full flex-wrap content-start items-start overflow-auto px-[25px] pb-[140px]"
ref="tabsRef"
@scroll="handleTabsScroll"
>
<div
class="mb-[20px] mr-[20px] inline-block"
v-for="role in roleList"
:key="role.id"
>
<Card
class="card"
class="relative rounded-[10px]"
:body-style="{
position: 'relative',
display: 'flex',
@ -75,7 +83,7 @@ const handleTabsScroll = async () => {
}"
>
<!-- 更多操作 -->
<div class="more-container" v-if="showMore">
<div v-if="showMore" class="absolute right-[12px] top-0">
<Dropdown>
<Button type="text">
<span class="icon-[ant-design--more-outlined] text-2xl"></span>
@ -91,23 +99,32 @@ const handleTabsScroll = async () => {
<Menu.Item @click="handleMoreClick(['delete', role])">
<div class="flex items-center">
<IconifyIcon icon="ep:delete" color="red" />
<span style="color: red">编辑</span>
<span class="text-red-500">编辑</span>
</div>
</Menu.Item>
</Menu>
</template>
</Dropdown>
</div>
<!-- 角色信息 -->
<div>
<img class="avatar" :src="role.avatar" />
<img
:src="role.avatar"
class="h-[40px] w-[40px] overflow-hidden rounded-[10px]"
/>
</div>
<div class="right-container">
<div class="content-container">
<div class="title">{{ role.name }}</div>
<div class="description">{{ role.description }}</div>
<div class="ml-[10px] w-full">
<div class="h-[85px]">
<div class="max-w-[140px] text-[18px] font-bold text-[#3e3e3e]">
{{ role.name }}
</div>
<div class="mt-[10px] text-[14px] text-[#6a6a6a]">
{{ role.description }}
</div>
</div>
<div class="btn-container">
<div class="mt-[2px] flex flex-row-reverse">
<Button type="primary" size="small" @click="handleUseClick(role)">
使用
</Button>
@ -117,67 +134,3 @@ const handleTabsScroll = async () => {
</div>
</div>
</template>
<style scoped lang="scss">
//
.card-list {
position: relative;
display: flex;
flex-flow: row wrap;
place-content: flex-start start;
align-items: start;
height: 100%;
padding: 0 25px;
padding-bottom: 140px;
overflow: auto;
.card {
position: relative;
display: inline-block;
margin-right: 20px;
margin-bottom: 20px;
border-radius: 10px;
.more-container {
position: absolute;
top: 0;
right: 12px;
}
.avatar {
width: 40px;
height: 40px;
overflow: hidden;
border-radius: 10px;
}
.right-container {
width: 100%;
margin-left: 10px;
//height: 100px;
.content-container {
height: 85px;
.title {
max-width: 140px;
font-size: 18px;
font-weight: bold;
color: #3e3e3e;
}
.description {
margin-top: 10px;
font-size: 14px;
color: #6a6a6a;
}
}
.btn-container {
display: flex;
flex-direction: row-reverse;
margin-top: 2px;
}
}
}
}
</style>

View File

@ -171,15 +171,18 @@ onMounted(async () => {
<template>
<Drawer>
<Layout class="role-container">
<Layout
class="absolute inset-0 flex h-full w-full flex-col overflow-hidden bg-white"
>
<FormModal @success="handlerAddRoleSuccess" />
<Layout.Content class="role-main">
<div class="search-container">
<!-- 搜索按钮 -->
<Layout.Content class="relative m-0 flex-1 overflow-hidden p-0">
<div class="absolute right-0 top-[-5px] z-[100] mr-[20px] mt-[20px]">
<!-- 搜索输入框 -->
<Input.Search
:loading="loading"
v-model:value="search"
class="search-input"
class="w-[240px]"
placeholder="请输入搜索的内容"
@search="getActiveTabsRole"
/>
@ -193,12 +196,18 @@ onMounted(async () => {
添加角色
</Button>
</div>
<!-- 标签页内容 -->
<Tabs
v-model:value="activeTab"
class="tabs p-4"
class="relative h-full p-4"
@tab-click="handleTabsClick"
>
<TabPane key="my-role" class="role-pane" tab="我的角色">
<TabPane
key="my-role"
class="flex h-full flex-col overflow-y-auto"
tab="我的角色"
>
<RoleList
:loading="loading"
:role-list="myRoleList"
@ -210,12 +219,17 @@ onMounted(async () => {
class="mt-[20px]"
/>
</TabPane>
<TabPane key="public-role" class="role-pane" tab="公共角色">
<TabPane
key="public-role"
class="flex h-full flex-col overflow-y-auto"
tab="公共角色"
>
<RoleCategoryList
class="role-category-list"
:category-list="categoryList"
:active="activeCategory"
@on-category-click="handlerCategoryClick"
class="mx-[27px]"
/>
<RoleList
:role-list="publicRoleList"
@ -232,56 +246,3 @@ onMounted(async () => {
</Layout>
</Drawer>
</template>
<style scoped lang="scss">
//
.role-container {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
background-color: #fff;
.role-main {
position: relative;
flex: 1;
padding: 0;
margin: 0;
overflow: hidden;
.search-container {
position: absolute;
top: -5px;
right: 0;
z-index: 100;
margin: 20px 20px 0;
}
.search-input {
width: 240px;
}
.tabs {
position: relative;
height: 100%;
.role-category-list {
margin: 0 27px;
}
}
.role-pane {
position: relative;
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
}
}
}
</style>

View File

@ -494,7 +494,7 @@ onMounted(async () => {
<template>
<Page auto-content-height>
<Layout class="ai-layout">
<Layout class="absolute left-0 top-0 h-full w-full flex-1">
<!-- 左侧对话列表 -->
<ConversationList
:active-id="activeConversationId"
@ -504,19 +504,24 @@ onMounted(async () => {
@on-conversation-clear="handleConversationClear"
@on-conversation-delete="handlerConversationDelete"
/>
<Layout class="detail-container">
<Layout.Header class="header">
<div class="title">
<!-- 右侧详情部分 -->
<Layout class="bg-white">
<Layout.Header
class="flex items-center justify-between bg-[#fbfbfb!important] shadow-none"
>
<div class="text-[18px] font-bold">
{{ activeConversation?.title ? activeConversation?.title : '对话' }}
<span v-if="activeMessageList.length > 0">
({{ activeMessageList.length }})
</span>
</div>
<div class="btns" v-if="activeConversation">
<div class="flex w-[300px] justify-end" v-if="activeConversation">
<Button
type="primary"
ghost
class="mr-[10px]"
class="mr-[10px] px-[10px]"
size="small"
@click="openChatConversationUpdateForm"
>
@ -525,7 +530,7 @@ onMounted(async () => {
</Button>
<Button
size="small"
class="btn mr-[10px]"
class="mr-[10px] px-[10px]"
@click="handlerMessageClear"
>
<IconifyIcon
@ -533,54 +538,52 @@ onMounted(async () => {
color="#787878"
/>
</Button>
<Button size="small" class="btn mr-[10px]">
<Button size="small" class="mr-[10px] px-[10px]">
<IconifyIcon icon="ep:download" color="#787878" />
</Button>
<Button
size="small"
class="btn mr-[10px]"
class="mr-[10px] px-[10px]"
@click="handleGoTopMessage"
>
<IconifyIcon icon="ep:top" color="#787878" />
</Button>
</div>
</Layout.Header>
<Layout.Content class="main-container">
<div>
<div class="message-container">
<!-- 情况一消息加载中 -->
<MessageLoading v-if="activeMessageListLoading" />
<!-- 情况二无聊天对话时 -->
<MessageNewConversation
v-if="!activeConversation"
@on-new-conversation="handleConversationCreate"
/>
<!-- 情况三消息列表为空 -->
<MessageListEmpty
v-if="
!activeMessageListLoading &&
messageList.length === 0 &&
activeConversation
"
@on-prompt="doSendMessage"
/>
<!-- 情况四消息列表不为空 -->
<MessageList
v-if="!activeMessageListLoading && messageList.length > 0"
ref="messageRef"
:conversation="activeConversation"
:list="messageList"
@on-delete-success="handleMessageDelete"
@on-edit="handleMessageEdit"
@on-refresh="handleMessageRefresh"
/>
</div>
<Layout.Content class="relative m-0 h-full w-full p-0">
<div class="absolute inset-0 m-0 overflow-y-hidden p-0">
<MessageLoading v-if="activeMessageListLoading" />
<MessageNewConversation
v-if="!activeConversation"
@on-new-conversation="handleConversationCreate"
/>
<MessageListEmpty
v-if="
!activeMessageListLoading &&
messageList.length === 0 &&
activeConversation
"
@on-prompt="doSendMessage"
/>
<MessageList
v-if="!activeMessageListLoading && messageList.length > 0"
ref="messageRef"
:conversation="activeConversation"
:list="messageList"
@on-delete-success="handleMessageDelete"
@on-edit="handleMessageEdit"
@on-refresh="handleMessageRefresh"
/>
</div>
</Layout.Content>
<Layout.Footer class="footer-container">
<form class="prompt-from">
<Layout.Footer class="m-0 flex flex-col bg-[white!important] p-0">
<form
class="m-[10px_20px_20px] flex flex-col rounded-[10px] border border-[#e3e3e3] p-[9px_10px]"
>
<textarea
class="prompt-input"
class="box-border h-[80px] resize-none overflow-auto border-none p-[0_2px] focus:outline-none"
v-model="prompt"
@keydown="handleSendByKeydown"
@input="handlePromptInput"
@ -588,10 +591,10 @@ onMounted(async () => {
@compositionend="onCompositionend"
placeholder="问我任何问题...Shift+Enter 换行,按下 Enter 发送)"
></textarea>
<div class="prompt-btns">
<div>
<div class="flex justify-between pb-0 pt-[5px]">
<div class="flex items-center">
<Switch v-model:checked="enableContext" />
<span class="ml-5px text-14px text-#8f8f8f">上下文</span>
<span class="ml-[5px] text-[14px] text-[#8f8f8f]">上下文</span>
</div>
<Button
type="primary"
@ -616,202 +619,3 @@ onMounted(async () => {
<FormModal @success="handleConversationUpdateSuccess" />
</Page>
</template>
<style lang="scss" scoped>
.ai-layout {
position: absolute;
top: 0;
left: 0;
flex: 1;
width: 100%;
height: 100%;
}
.conversation-container {
position: relative;
display: flex;
flex-direction: column;
justify-content: space-between;
padding: 10px 10px 0;
.btn-new-conversation {
padding: 18px 0;
}
.search-input {
margin-top: 20px;
}
.conversation-list {
margin-top: 20px;
.conversation {
display: flex;
flex: 1;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: 0 5px;
margin-top: 10px;
line-height: 30px;
cursor: pointer;
border-radius: 5px;
&.active {
background-color: #e6e6e6;
.button {
display: inline-block;
}
}
.title-wrapper {
display: flex;
flex-direction: row;
align-items: center;
}
.title {
max-width: 220px;
padding: 5px 10px;
overflow: hidden;
text-overflow: ellipsis;
font-size: 14px;
white-space: nowrap;
}
.avatar {
display: flex;
flex-direction: row;
justify-items: center;
width: 28px;
height: 28px;
}
//
.button-wrapper {
right: 2px;
display: flex;
flex-direction: row;
justify-items: center;
color: #606266;
.el-icon {
margin-right: 5px;
}
}
}
}
//
.tool-box {
display: flex;
align-items: center;
justify-content: space-between;
line-height: 35px;
color: var(--el-text-color);
> div {
display: flex;
align-items: center;
padding: 0;
margin: 0;
color: #606266;
cursor: pointer;
> span {
margin-left: 5px;
}
}
}
}
//
.detail-container {
background: #fff;
.header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
background: #fbfbfb;
box-shadow: 0 0 0 0 #dcdfe6;
.title {
font-size: 18px;
font-weight: bold;
}
.btns {
display: flex;
flex-direction: row;
justify-content: flex-end;
width: 300px;
//justify-content: space-between;
.btn {
padding: 0 10px;
}
}
}
}
// main
.main-container {
position: relative;
width: 100%;
height: 100%;
padding: 0;
margin: 0;
.message-container {
position: absolute;
inset: 0;
padding: 0;
margin: 0;
overflow-y: hidden;
}
}
//
.footer-container {
display: flex;
flex-direction: column;
height: auto;
padding: 0;
margin: 0;
background-color: white;
.prompt-from {
display: flex;
flex-direction: column;
height: auto;
padding: 9px 10px;
margin: 10px 20px 20px;
border: 1px solid #e3e3e3;
border-radius: 10px;
}
.prompt-input {
box-sizing: border-box;
height: 80px;
padding: 0 2px;
overflow: auto;
resize: none;
//box-shadow: none;
border: none;
}
.prompt-input:focus {
outline: none;
}
.prompt-btns {
display: flex;
justify-content: space-between;
padding-top: 5px;
padding-bottom: 0;
}
}
</style>

View File

@ -65,8 +65,12 @@ onMounted(async () => {
});
</script>
<template>
<Card body-class="" class="image-card">
<div class="image-operation">
<Card
body-class=""
class="relative flex h-auto w-[320px] flex-col rounded-[10px]"
>
<!-- 图片操作区 -->
<div class="flex flex-row justify-between">
<div>
<Button v-if="detail?.status === AiImageStatusEnum.IN_PROGRESS">
生成中
@ -78,31 +82,30 @@ onMounted(async () => {
异常
</Button>
</div>
<!-- 操作区 -->
<div>
<div class="flex">
<Button
class="btn"
class="m-0 p-[10px]"
type="text"
@click="handleButtonClick('download', detail)"
>
<span class="icon-[ant-design--download-outlined]"></span>
</Button>
<Button
class="btn"
class="m-0 p-[10px]"
type="text"
@click="handleButtonClick('regeneration', detail)"
>
<span class="icon-[ant-design--redo-outlined]"></span>
</Button>
<Button
class="btn"
class="m-0 p-[10px]"
type="text"
@click="handleButtonClick('delete', detail)"
>
<span class="icon-[ant-design--delete-outlined]"></span>
</Button>
<Button
class="btn"
class="m-0 p-[10px]"
type="text"
@click="handleButtonClick('more', detail)"
>
@ -110,14 +113,17 @@ onMounted(async () => {
</Button>
</div>
</div>
<div class="image-wrapper" ref="cardImageRef">
<Image class="image" :src="detail?.picUrl" />
<!-- 图片展示区域 -->
<div class="mt-[20px] h-[280px] flex-1 overflow-hidden" ref="cardImageRef">
<Image class="w-full rounded-[10px]" :src="detail?.picUrl" />
<div v-if="detail?.status === AiImageStatusEnum.FAIL">
{{ detail?.errorMessage }}
</div>
</div>
<!-- Midjourney 专属操作 -->
<div class="image-mj-btns">
<!-- Midjourney 专属操作按钮 -->
<div class="mt-[5px] flex w-full flex-wrap justify-start">
<Button
size="small"
v-for="(button, index) in detail?.buttons"
@ -130,46 +136,3 @@ onMounted(async () => {
</div>
</Card>
</template>
<style scoped lang="scss">
.image-card {
position: relative;
display: flex;
flex-direction: column;
width: 320px;
height: auto;
border-radius: 10px;
.image-operation {
display: flex;
flex-direction: row;
justify-content: space-between;
.btn {
//border: 1px solid red;
padding: 10px;
margin: 0;
}
}
.image-wrapper {
flex: 1;
height: 280px;
margin-top: 20px;
overflow: hidden;
.image {
width: 100%;
border-radius: 10px;
}
}
.image-mj-btns {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
width: 100%;
margin-top: 5px;
}
}
</style>

View File

@ -1,6 +1,5 @@
<script setup lang="ts">
import type { AiImageApi } from '#/api/ai/image';
import type { ImageModelVO } from '#/utils/constants';
import { ref, toRefs, watch } from 'vue';
@ -43,15 +42,16 @@ watch(
</script>
<template>
<div class="item">
<div class="body">
<Image class="image" :src="detail?.picUrl" />
<div class="mb-5 w-full overflow-hidden break-words">
<div class="body mt-2 text-gray-600">
<Image class="rounded-[10px]" :src="detail?.picUrl" />
</div>
</div>
<!-- 时间 -->
<div class="item">
<div class="tip">时间</div>
<div class="body">
<div class="mb-5 w-full overflow-hidden break-words">
<div class="tip text-lg font-bold">时间</div>
<div class="body mt-2 text-gray-600">
<div>
提交时间{{ formatTime(detail.createTime, 'yyyy-MM-dd HH:mm:ss') }}
</div>
@ -60,177 +60,150 @@ watch(
</div>
</div>
</div>
<!-- 模型 -->
<div class="item">
<div class="tip">模型</div>
<div class="body">
<div class="mb-5 w-full overflow-hidden break-words">
<div class="tip text-lg font-bold">模型</div>
<div class="body mt-2 text-gray-600">
{{ detail.model }}({{ detail.height }}x{{ detail.width }})
</div>
</div>
<!-- 提示词 -->
<div class="item">
<div class="tip">提示词</div>
<div class="body">
<div class="mb-5 w-full overflow-hidden break-words">
<div class="tip text-lg font-bold">提示词</div>
<div class="body mt-2 text-gray-600">
{{ detail.prompt }}
</div>
</div>
<!-- 地址 -->
<div class="item">
<div class="tip">图片地址</div>
<div class="body">
<!-- 图片地址 -->
<div class="mb-5 w-full overflow-hidden break-words">
<div class="tip text-lg font-bold">图片地址</div>
<div class="body mt-2 text-gray-600">
{{ detail.picUrl }}
</div>
</div>
<!-- StableDiffusion 专属区域 -->
<!-- StableDiffusion 专属 -->
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION &&
detail?.options?.sampler
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">采样方法</div>
<div class="body">
<div class="tip text-lg font-bold">采样方法</div>
<div class="body mt-2 text-gray-600">
{{
StableDiffusionSamplers.find(
(item: ImageModelVO) => item.key === detail?.options?.sampler,
(item) => item.key === detail?.options?.sampler,
)?.name
}}
</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION &&
detail?.options?.clipGuidancePreset
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">CLIP</div>
<div class="body">
<div class="tip text-lg font-bold">CLIP</div>
<div class="body mt-2 text-gray-600">
{{
StableDiffusionClipGuidancePresets.find(
(item: ImageModelVO) =>
item.key === detail?.options?.clipGuidancePreset,
(item) => item.key === detail?.options?.clipGuidancePreset,
)?.name
}}
</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION &&
detail?.options?.stylePreset
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">风格</div>
<div class="body">
<div class="tip text-lg font-bold">风格</div>
<div class="body mt-2 text-gray-600">
{{
StableDiffusionStylePresets.find(
(item: ImageModelVO) => item.key === detail?.options?.stylePreset,
(item) => item.key === detail?.options?.stylePreset,
)?.name
}}
</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION &&
detail?.options?.steps
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">迭代步数</div>
<div class="body">
{{ detail?.options?.steps }}
</div>
<div class="tip text-lg font-bold">迭代步数</div>
<div class="body mt-2 text-gray-600">{{ detail?.options?.steps }}</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION &&
detail?.options?.scale
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">引导系数</div>
<div class="body">
{{ detail?.options?.scale }}
</div>
<div class="tip text-lg font-bold">引导系数</div>
<div class="body mt-2 text-gray-600">{{ detail?.options?.scale }}</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.STABLE_DIFFUSION &&
detail?.options?.seed
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">随机因子</div>
<div class="body">
{{ detail?.options?.seed }}
</div>
<div class="tip text-lg font-bold">随机因子</div>
<div class="body mt-2 text-gray-600">{{ detail?.options?.seed }}</div>
</div>
<!-- Dall3 专属区域 -->
<!-- Dall3 专属 -->
<div
class="item"
v-if="detail.platform === AiPlatformEnum.OPENAI && detail?.options?.style"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">风格选择</div>
<div class="body">
<div class="tip text-lg font-bold">风格选择</div>
<div class="body mt-2 text-gray-600">
{{
Dall3StyleList.find(
(item: ImageModelVO) => item.key === detail?.options?.style,
)?.name
Dall3StyleList.find((item) => item.key === detail?.options?.style)?.name
}}
</div>
</div>
<!-- Midjourney 专属区域 -->
<!-- Midjourney 专属 -->
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.MIDJOURNEY && detail?.options?.version
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">模型版本</div>
<div class="body">
{{ detail?.options?.version }}
</div>
<div class="tip text-lg font-bold">模型版本</div>
<div class="body mt-2 text-gray-600">{{ detail?.options?.version }}</div>
</div>
<div
class="item"
v-if="
detail.platform === AiPlatformEnum.MIDJOURNEY &&
detail?.options?.referImageUrl
"
class="mb-5 w-full overflow-hidden break-words"
>
<div class="tip">参考图</div>
<div class="body">
<div class="tip text-lg font-bold">参考图</div>
<div class="body mt-2 text-gray-600">
<Image :src="detail.options.referImageUrl" />
</div>
</div>
</template>
<style scoped lang="scss">
.item {
width: 100%;
margin-bottom: 20px;
overflow: hidden;
word-wrap: break-word;
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.tip {
font-size: 16px;
font-weight: bold;
}
.body {
margin-top: 10px;
color: #616161;
.taskImage {
border-radius: 10px;
}
}
}
</style>

View File

@ -166,38 +166,46 @@ onUnmounted(async () => {
}
});
</script>
<template>
<Drawer class="w-[600px]">
<ImageDetail :id="showImageDetailId" />
</Drawer>
<Card
class="dr-task"
class="dr-task flex h-full w-full flex-col"
:body-style="{
margin: 0,
padding: 0,
height: '100%',
position: 'relative',
display: 'flex',
flexDirection: 'column',
}"
>
<template #title>
绘画任务
<!-- TODO @fan看看怎么优化下这个样子哈 -->
<Button @click="handleViewPublic"></Button>
</template>
<div class="task-image-list" ref="imageListRef">
<div
class="task-image-list flex flex-1 flex-wrap content-start overflow-y-auto p-5 pb-[140px] pt-5"
ref="imageListRef"
>
<ImageCard
v-for="image in imageList"
:key="image.id"
:detail="image"
@on-btn-click="handleImageButtonClick"
@on-mj-btn-click="handleImageMidjourneyButtonClick"
class="mb-5 mr-5"
/>
</div>
<div class="task-image-pagination">
<div
class="task-image-pagination sticky bottom-0 z-50 flex h-[60px] items-center justify-center bg-white shadow-[0_-2px_8px_rgba(0,0,0,0.1)]"
>
<Pagination
:total="pageTotal"
:show-total="(total: number) => `共 ${total} 条`"
:show-total="(total) => `共 ${total} 条`"
show-quick-jumper
show-size-changer
v-model:current="queryParams.pageNo"
@ -208,43 +216,3 @@ onUnmounted(async () => {
</div>
</Card>
</template>
<style lang="scss">
.dr-task {
width: 100%;
height: 100%;
}
.task-image-list {
position: relative;
box-sizing: border-box; /* 确保内边距不会增加高度 */
display: flex;
flex-flow: row wrap;
align-content: flex-start;
height: 100%;
padding: 20px 20px 140px;
overflow: auto;
> div {
margin-right: 20px;
margin-bottom: 20px;
}
> div:last-of-type {
//margin-bottom: 100px;
}
}
.task-image-pagination {
position: absolute;
bottom: 60px;
z-index: 999;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
height: 50px;
line-height: 90px;
background-color: #fff;
}
</style>

View File

@ -117,19 +117,20 @@ defineExpose({ settingValues });
v-model:value="prompt"
:maxlength="1024"
:rows="5"
class="w-100% mt-[15px]"
class="mt-[15px] w-full"
placeholder="例如:童话里的小屋应该是什么样子?"
show-count
/>
</div>
<div class="hot-words">
<div class="hot-words mt-[30px] flex flex-col">
<div>
<b>随机热词</b>
</div>
<Space wrap class="word-list">
<Space wrap class="word-list mt-[15px] flex flex-wrap justify-start">
<Button
shape="round"
class="btn"
class="btn m-0"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotWords"
:key="hotWord"
@ -139,16 +140,17 @@ defineExpose({ settingValues });
</Button>
</Space>
</div>
<div class="group-item">
<div class="group-item mt-[30px]">
<div>
<b>平台</b>
</div>
<Space wrap class="group-item-body">
<Space wrap class="group-item-body mt-[15px] w-full">
<Select
v-model:value="otherPlatform"
placeholder="Select"
size="large"
class="!w-[330px]"
class="!important w-[330px]"
@change="handlerPlatformChange"
>
<Select.Option
@ -161,16 +163,17 @@ defineExpose({ settingValues });
</Select>
</Space>
</div>
<div class="group-item">
<div class="group-item mt-[30px]">
<div>
<b>模型</b>
</div>
<Space wrap class="group-item-body">
<Space wrap class="group-item-body mt-[15px] w-full">
<Select
v-model:value="modelId"
placeholder="Select"
size="large"
class="!w-[330px]"
class="!important w-[330px]"
>
<Select.Option
v-for="item in platformModels"
@ -182,11 +185,12 @@ defineExpose({ settingValues });
</Select>
</Space>
</div>
<div class="group-item">
<div class="group-item mt-[30px]">
<div>
<b>图片尺寸</b>
</div>
<Space wrap class="group-item-body">
<Space wrap class="group-item-body mt-[15px] flex flex-wrap gap-x-[20px]">
<InputNumber
v-model:value="width"
class="mt-[10px] w-[170px]"
@ -199,7 +203,8 @@ defineExpose({ settingValues });
/>
</Space>
</div>
<div class="btns">
<div class="btns mt-[50px] flex justify-center">
<Button
type="primary"
size="large"
@ -212,37 +217,3 @@ defineExpose({ settingValues });
</Button>
</div>
</template>
<style scoped lang="scss">
.hot-words {
display: flex;
flex-direction: column;
margin-top: 30px;
.word-list {
display: flex;
flex-flow: row wrap;
justify-content: start;
margin-top: 15px;
.btn {
margin: 0;
}
}
}
//
.group-item {
margin-top: 30px;
.group-item-body {
width: 100%;
margin-top: 15px;
}
}
.btns {
display: flex;
justify-content: center;
margin-top: 50px;
}
</style>

View File

@ -151,19 +151,18 @@ defineExpose({ settingValues });
v-model:value="prompt"
:maxlength="1024"
:rows="5"
class="w-100% mt-[15px]"
class="mt-[15px] w-full"
placeholder="例如:童话里的小屋应该是什么样子?"
show-count
/>
</div>
<div class="hot-words">
<div>
<b>随机热词</b>
</div>
<Space wrap class="word-list">
<div class="hot-words mt-[30px] flex flex-col">
<div><b>随机热词</b></div>
<Space wrap class="word-list mt-[15px] flex flex-wrap justify-start">
<Button
shape="round"
class="btn"
class="btn m-0"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotWords"
:key="hotWord"
@ -173,15 +172,17 @@ defineExpose({ settingValues });
</Button>
</Space>
</div>
<div class="model">
<div>
<b>模型选择</b>
</div>
<Space wrap class="model-list">
<div class="model mt-[30px]">
<div><b>模型选择</b></div>
<Space wrap class="model-list mt-[15px] flex flex-wrap gap-[10px]">
<div
:class="
selectModel === model.key ? 'modal-item selectModel' : 'modal-item'
"
class="modal-item flex w-[110px] cursor-pointer flex-col items-center overflow-hidden rounded-[5px] border-[3px]"
:class="[
selectModel === model.key
? 'border-[#1293ff!important]'
: 'border-transparent',
]"
v-for="model in Dall3Models"
:key="model.key"
>
@ -191,21 +192,21 @@ defineExpose({ settingValues });
fit="contain"
@click="handleModelClick(model)"
/>
<div class="model-font">{{ model.name }}</div>
<div class="model-font text-[14px] font-bold text-[#3e3e3e]">
{{ model.name }}
</div>
</div>
</Space>
</div>
<div class="image-style">
<div>
<b>风格选择</b>
</div>
<Space wrap class="image-style-list">
<div class="image-style mt-[30px]">
<div><b>风格选择</b></div>
<Space wrap class="image-style-list mt-[15px] flex flex-wrap gap-[10px]">
<div
:class="
style === imageStyle.key
? 'image-style-item selectImageStyle'
: 'image-style-item'
"
class="image-style-item flex w-[110px] cursor-pointer flex-col items-center overflow-hidden rounded-[5px] border-[3px]"
:class="[
style === imageStyle.key ? 'border-[#1293ff]' : 'border-transparent',
]"
v-for="imageStyle in Dall3StyleList"
:key="imageStyle.key"
>
@ -215,35 +216,41 @@ defineExpose({ settingValues });
fit="contain"
@click="handleStyleClick(imageStyle)"
/>
<div class="style-font">{{ imageStyle.name }}</div>
<div class="style-font text-[14px] font-bold text-[#3e3e3e]">
{{ imageStyle.name }}
</div>
</div>
</Space>
</div>
<div class="image-size">
<div>
<b>画面比例</b>
</div>
<Space wrap class="size-list">
<div class="image-size mt-[30px] w-full">
<div><b>画面比例</b></div>
<Space
wrap
class="size-list mt-[20px] flex w-full flex-row justify-between"
>
<div
class="size-item"
class="size-item flex cursor-pointer flex-col items-center"
v-for="imageSize in Dall3SizeList"
:key="imageSize.key"
@click="handleSizeClick(imageSize)"
>
<div
:class="
selectSize === imageSize.key
? 'size-wrapper selectImageSize'
: 'size-wrapper'
"
class="size-wrapper flex h-[50px] w-[50px] flex-col items-center justify-center rounded-[7px] border bg-white p-[4px]"
:class="[
selectSize === imageSize.key ? 'border-[#1293ff]' : 'border-white',
]"
>
<div :style="imageSize.style"></div>
</div>
<div class="size-font">{{ imageSize.name }}</div>
<div class="size-font text-[14px] font-bold text-[#3e3e3e]">
{{ imageSize.name }}
</div>
</div>
</Space>
</div>
<div class="btns">
<div class="btns mt-[50px] flex justify-center">
<Button
type="primary"
size="large"
@ -256,134 +263,3 @@ defineExpose({ settingValues });
</Button>
</div>
</template>
<style scoped lang="scss">
//
.hot-words {
display: flex;
flex-direction: column;
margin-top: 30px;
.word-list {
display: flex;
flex-flow: row wrap;
justify-content: start;
margin-top: 15px;
.btn {
margin: 0;
}
}
}
//
.model {
margin-top: 30px;
.model-list {
margin-top: 15px;
.modal-item {
display: flex;
flex-direction: column;
align-items: center;
width: 110px;
//outline: 1px solid blue;
overflow: hidden;
cursor: pointer;
border: 3px solid transparent;
.model-font {
font-size: 14px;
font-weight: bold;
color: #3e3e3e;
}
}
.selectModel {
border: 3px solid #1293ff;
border-radius: 5px;
}
}
}
// style
.image-style {
margin-top: 30px;
.image-style-list {
margin-top: 15px;
.image-style-item {
display: flex;
flex-direction: column;
align-items: center;
width: 110px;
//outline: 1px solid blue;
overflow: hidden;
cursor: pointer;
border: 3px solid transparent;
.style-font {
font-size: 14px;
font-weight: bold;
color: #3e3e3e;
}
}
.selectImageStyle {
border: 3px solid #1293ff;
border-radius: 5px;
}
}
}
//
.image-size {
width: 100%;
margin-top: 30px;
.size-list {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
margin-top: 20px;
.size-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
.size-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
padding: 4px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 7px;
}
.size-font {
font-size: 14px;
font-weight: bold;
color: #3e3e3e;
}
}
}
.selectImageSize {
border: 1px solid #1293ff !important;
}
}
.btns {
display: flex;
justify-content: center;
margin-top: 50px;
}
</style>

View File

@ -150,19 +150,18 @@ defineExpose({ settingValues });
v-model:value="prompt"
:maxlength="1024"
:rows="5"
class="w-100% mt-[15px]"
class="mt-[15px] w-full"
placeholder="例如:童话里的小屋应该是什么样子?"
show-count
/>
</div>
<div class="hot-words">
<div>
<b>随机热词</b>
</div>
<Space wrap class="word-list">
<div class="mt-8 flex flex-col">
<div><b>随机热词</b></div>
<Space wrap class="mt-4 flex flex-wrap gap-2">
<Button
shape="round"
class="btn"
class="m-0"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotWords"
:key="hotWord"
@ -172,41 +171,41 @@ defineExpose({ settingValues });
</Button>
</Space>
</div>
<div class="image-size">
<div>
<b>尺寸</b>
</div>
<Space wrap class="size-list">
<div class="mt-8 w-full">
<div><b>尺寸</b></div>
<Space wrap class="mt-5 flex w-full flex-row justify-between">
<div
class="size-item"
class="flex cursor-pointer flex-col items-center"
v-for="imageSize in MidjourneySizeList"
:key="imageSize.key"
@click="handleSizeClick(imageSize)"
>
<div
:class="
selectSize === imageSize.key
? 'size-wrapper selectImageSize'
: 'size-wrapper'
"
class="flex h-[50px] w-[50px] items-center justify-center rounded-[7px] border bg-white p-1"
:class="[
selectSize === imageSize.key ? 'border-[#1293ff]' : 'border-white',
]"
>
<div :style="imageSize.style"></div>
</div>
<div class="size-font">{{ imageSize.key }}</div>
<div class="text-sm font-bold text-[#3e3e3e]">{{ imageSize.key }}</div>
</div>
</Space>
</div>
<div class="model">
<div>
<b>模型</b>
</div>
<Space wrap class="model-list">
<div class="mt-8">
<div><b>模型</b></div>
<Space wrap class="mt-4 flex flex-wrap gap-4">
<div
:class="
selectModel === model.key ? 'modal-item selectModel' : 'modal-item'
"
v-for="model in MidjourneyModels"
:key="model.key"
class="flex w-[150px] cursor-pointer flex-col items-center overflow-hidden border-[3px]"
:class="[
selectModel === model.key
? 'rounded border-[#1293ff]'
: 'border-transparent',
]"
>
<Image
:preview="false"
@ -214,18 +213,17 @@ defineExpose({ settingValues });
fit="contain"
@click="handleModelClick(model)"
/>
<div class="model-font">{{ model.name }}</div>
<div class="text-sm font-bold text-[#3e3e3e]">{{ model.name }}</div>
</div>
</Space>
</div>
<div class="version">
<div>
<b>版本</b>
</div>
<Space wrap class="version-list">
<div class="mt-5">
<div><b>版本</b></div>
<Space wrap class="mt-5 w-full">
<Select
v-model:value="selectVersion"
class="version-select !w-[330px]"
class="!w-[330px]"
clearable
placeholder="请选择版本"
>
@ -239,15 +237,15 @@ defineExpose({ settingValues });
</Select>
</Space>
</div>
<div class="model">
<div>
<b>参考图</b>
</div>
<Space wrap class="model-list">
<div class="mt-8">
<div><b>参考图</b></div>
<Space wrap class="mt-4">
<ImageUpload v-model:value="referImageUrl" :show-description="false" />
</Space>
</div>
<div class="btns">
<div class="mt-[50px] flex justify-center">
<Button
type="primary"
size="large"
@ -259,113 +257,3 @@ defineExpose({ settingValues });
</Button>
</div>
</template>
<style scoped lang="scss">
//
.hot-words {
display: flex;
flex-direction: column;
margin-top: 30px;
.word-list {
display: flex;
flex-flow: row wrap;
justify-content: start;
margin-top: 15px;
.btn {
margin: 0;
}
}
}
// version
.version {
margin-top: 20px;
.version-list {
width: 100%;
margin-top: 20px;
}
}
//
.model {
margin-top: 30px;
.model-list {
margin-top: 15px;
.modal-item {
display: flex;
flex-direction: column;
align-items: center;
width: 150px;
//outline: 1px solid blue;
overflow: hidden;
cursor: pointer;
border: 3px solid transparent;
.model-font {
font-size: 14px;
font-weight: bold;
color: #3e3e3e;
}
}
.selectModel {
border: 3px solid #1293ff;
border-radius: 5px;
}
}
}
//
.image-size {
width: 100%;
margin-top: 30px;
.size-list {
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
margin-top: 20px;
.size-item {
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
.size-wrapper {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
padding: 4px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 7px;
}
.size-font {
font-size: 14px;
font-weight: bold;
color: #3e3e3e;
}
}
}
.selectImageSize {
border: 1px solid #1293ff !important;
}
}
.btns {
display: flex;
justify-content: center;
margin-top: 50px;
}
</style>

View File

@ -9,7 +9,6 @@ import { alert, confirm } from '@vben/common-ui';
import {
Button,
Input,
InputNumber,
message,
Select,
@ -139,19 +138,19 @@ defineExpose({ settingValues });
v-model:value="prompt"
:maxlength="1024"
:rows="5"
class="w-100% mt-[15px]"
class="mt-[15px] w-full"
placeholder="例如:童话里的小屋应该是什么样子?"
show-count
/>
</div>
<div class="hot-words">
<div>
<b>随机热词</b>
</div>
<Space wrap class="word-list">
<!-- 热词区域 -->
<div class="mt-[30px] flex flex-col">
<div><b>随机热词</b></div>
<Space wrap class="mt-[15px] flex flex-wrap justify-start">
<Button
shape="round"
class="btn"
class="m-0"
:type="selectHotWord === hotWord ? 'primary' : 'default'"
v-for="hotWord in ImageHotEnglishWords"
:key="hotWord"
@ -161,11 +160,11 @@ defineExpose({ settingValues });
</Button>
</Space>
</div>
<div class="group-item">
<div>
<b>采样方法</b>
</div>
<Space wrap class="group-item-body">
<!-- 参数项采样方法 -->
<div class="mt-[30px]">
<div><b>采样方法</b></div>
<Space wrap class="mt-[15px] w-full">
<Select
v-model:value="sampler"
placeholder="Select"
@ -182,11 +181,11 @@ defineExpose({ settingValues });
</Select>
</Space>
</div>
<div class="group-item">
<div>
<b>CLIP</b>
</div>
<Space wrap class="group-item-body">
<!-- CLIP -->
<div class="mt-[30px]">
<div><b>CLIP</b></div>
<Space wrap class="mt-[15px] w-full">
<Select
v-model:value="clipGuidancePreset"
placeholder="Select"
@ -203,11 +202,11 @@ defineExpose({ settingValues });
</Select>
</Space>
</div>
<div class="group-item">
<div>
<b>风格</b>
</div>
<Space wrap class="group-item-body">
<!-- 风格 -->
<div class="mt-[30px]">
<div><b>风格</b></div>
<Space wrap class="mt-[15px] w-full">
<Select
v-model:value="stylePreset"
placeholder="Select"
@ -225,35 +224,43 @@ defineExpose({ settingValues });
</Select>
</Space>
</div>
<div class="group-item">
<div>
<b>图片尺寸</b>
</div>
<Space wrap class="group-item-body">
<Input v-model="width" class="w-[170px]" placeholder="图片宽度" />
<Input v-model="height" class="w-[170px]" placeholder="图片高度" />
<!-- 图片尺寸 -->
<div class="mt-[30px]">
<div><b>图片尺寸</b></div>
<Space wrap class="mt-[15px] w-full">
<InputNumber
v-model:value="width"
class="w-[170px]"
placeholder="图片宽度"
/>
<InputNumber
v-model:value="height"
class="w-[170px]"
placeholder="图片高度"
/>
</Space>
</div>
<div class="group-item">
<div>
<b>迭代步数</b>
</div>
<Space wrap class="group-item-body">
<!-- 迭代步数 -->
<div class="mt-[30px]">
<div><b>迭代步数</b></div>
<Space wrap class="mt-[15px] w-full">
<InputNumber
v-model="steps"
v-model:value="steps"
size="large"
class="!w-[330px]"
placeholder="Please input"
/>
</Space>
</div>
<div class="group-item">
<div>
<b>引导系数</b>
</div>
<Space wrap class="group-item-body">
<!-- 引导系数 -->
<div class="mt-[30px]">
<div><b>引导系数</b></div>
<Space wrap class="mt-[15px] w-full">
<InputNumber
v-model="scale"
v-model:value="scale"
type="number"
size="large"
class="!w-[330px]"
@ -261,11 +268,11 @@ defineExpose({ settingValues });
/>
</Space>
</div>
<div class="group-item">
<div>
<b>随机因子</b>
</div>
<Space wrap class="group-item-body">
<!-- 随机因子 -->
<div class="mt-[30px]">
<div><b>随机因子</b></div>
<Space wrap class="mt-[15px] w-full">
<InputNumber
v-model:value="seed"
size="large"
@ -274,7 +281,9 @@ defineExpose({ settingValues });
/>
</Space>
</div>
<div class="btns">
<!-- 生成按钮 -->
<div class="mt-[50px] flex justify-center">
<Button
type="primary"
size="large"
@ -287,38 +296,3 @@ defineExpose({ settingValues });
</Button>
</div>
</template>
<style scoped lang="scss">
//
.hot-words {
display: flex;
flex-direction: column;
margin-top: 30px;
.word-list {
display: flex;
flex-flow: row wrap;
justify-content: start;
margin-top: 15px;
.btn {
margin: 0;
}
}
}
//
.group-item {
margin-top: 30px;
.group-item-body {
width: 100%;
margin-top: 15px;
}
}
.btns {
display: flex;
justify-content: center;
margin-top: 50px;
}
</style>

View File

@ -90,15 +90,16 @@ onMounted(async () => {
<template>
<Page auto-content-height>
<div class="ai-image">
<div class="left">
<div class="ai-image absolute inset-0 flex h-full w-full flex-row">
<div class="left flex w-[390px] flex-col p-5">
<div class="segmented flex justify-center">
<Segmented
v-model:value="selectPlatform"
:options="platformOptions"
class="bg-[#ececec]"
/>
</div>
<div class="modal-switch-container">
<div class="modal-switch-container mt-[30px] h-full overflow-y-auto">
<Common
v-if="selectPlatform === 'common'"
ref="commonRef"
@ -125,47 +126,9 @@ onMounted(async () => {
/>
</div>
</div>
<div class="main">
<div class="main flex-1 bg-white">
<ImageList ref="imageListRef" @on-regeneration="handleRegeneration" />
</div>
</div>
</Page>
</template>
<style scoped lang="scss">
.ai-image {
position: absolute;
inset: 0;
display: flex;
flex-direction: row;
width: 100%;
height: 100%;
.left {
display: flex;
flex-direction: column;
width: 390px;
padding: 20px;
.segmented .ant-segmented {
background-color: #ececec;
}
.modal-switch-container {
height: 100%;
margin-top: 30px;
overflow-y: auto;
}
}
.main {
flex: 1;
background-color: #fff;
}
.right {
width: 350px;
background-color: #f7f8fa;
}
}
</style>

View File

@ -45,72 +45,44 @@ onMounted(async () => {
</script>
<template>
<Page auto-content-height>
<div class="square-container">
<!-- TODO @fanstyle 建议换成 unocss -->
<div class="bg-[#fff] p-[20px]">
<!-- TODO @fanSearch 可以换成 Icon 组件么 -->
<Input.Search
v-model="queryParams.prompt"
style="width: 100%; margin-bottom: 20px"
class="mb-[20px] w-full"
size="large"
placeholder="请输入要搜索的内容"
@keyup.enter="handleQuery"
/>
<div class="gallery">
<div
class="grid gap-[10px] bg-[#fff] shadow-[0_0_10px_rgba(0,0,0,0.1)]"
style="grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))"
>
<!-- TODO @fan这个图片的风格要不和 ImageCard.vue 界面一致只有卡片没有操作因为看着更有相框的感觉~~~ -->
<div v-for="item in list" :key="item.id" class="gallery-item">
<img :src="item.picUrl" class="img" />
<div
v-for="item in list"
:key="item.id"
class="relative cursor-pointer overflow-hidden bg-[#f0f0f0] transition-transform duration-300 hover:scale-[1.05]"
>
<img
:src="item.picUrl"
class="block h-auto w-full transition-transform duration-300 hover:scale-[1.1]"
/>
</div>
</div>
<!-- TODO @fan缺少翻页 -->
<!-- 分页 -->
<Pagination
:total="total"
:show-total="(total: number) => `共 ${total} 条`"
:show-total="(total) => `共 ${total} 条`"
show-quick-jumper
show-size-changer
v-model:current="queryParams.pageNo"
v-model:page-size="queryParams.pageSize"
@change="debounceGetList"
@show-size-change="debounceGetList"
class="mt-[20px]"
/>
</div>
</Page>
</template>
<style scoped lang="scss">
.square-container {
padding: 20px;
background-color: #fff;
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 10px;
//max-width: 1000px;
background-color: #fff;
box-shadow: 0 0 10px rgb(0 0 0 / 10%);
}
.gallery-item {
position: relative;
overflow: hidden;
cursor: pointer;
background: #f0f0f0;
transition: transform 0.3s;
}
.gallery-item img {
display: block;
width: 100%;
height: auto;
transition: transform 0.3s;
}
.gallery-item:hover img {
transform: scale(1.1);
}
.gallery-item:hover {
transform: scale(1.05);
}
}
</style>

View File

@ -24,7 +24,11 @@ defineExpose({
</script>
<template>
<div class="flex w-[350px] flex-col bg-[#f5f7f9] p-5">
<h3 class="title w-full text-center leading-[28px]">思维导图创作中心</h3>
<h3
class="h-[1.75rem] w-full text-center text-[1.25rem] leading-[28px] text-[hsl(var(--primary))]"
>
思维导图创作中心
</h3>
<div class="flex-grow overflow-y-auto">
<div>
<b>您的需求</b>
@ -67,11 +71,3 @@ defineExpose({
</div>
</div>
</template>
<style lang="scss" scoped>
.title {
height: 1.75rem;
font-size: 1.25rem;
color: hsl(var(--primary));
}
</style>

View File

@ -120,7 +120,7 @@ defineExpose({
</script>
<template>
<Card class="my-card h-full flex-grow">
<Card class="my-card flex h-full flex-grow flex-col">
<template #title>
<div class="m-0 flex shrink-0 items-center justify-between px-7">
<h3>思维导图预览</h3>
@ -176,9 +176,6 @@ defineExpose({
}
.my-card {
display: flex;
flex-direction: column;
:deep(.ant-card-body) {
box-sizing: border-box;
flex-grow: 1;

View File

@ -8,7 +8,7 @@ import { useVbenDrawer } from '@vben/common-ui';
import { Button, Input, Select } from 'ant-design-vue';
import { testWorkflow } from '#/api/ai/workflow';
import Tinyflow from '#/components/Tinyflow/Tinyflow.vue';
import Tinyflow from '#/components/tinyflow/tinyflow.vue';
defineProps<{
provider: any;
@ -25,7 +25,7 @@ const error = ref(null);
const [Drawer, drawerApi] = useVbenDrawer({
footer: false,
closeOnClickModal: false,
modal: false
modal: false,
});
/** 展示工作流测试抽屉 */
const testWorkflowModel = () => {
@ -180,7 +180,7 @@ defineExpose({ validate });
</script>
<template>
<div class="relative" style="width: 100%; height: 700px">
<div class="relative h-[700px] w-[100%]">
<Tinyflow
v-if="workflowData"
ref="tinyflowRef"
@ -198,12 +198,19 @@ defineExpose({ validate });
测试
</Button>
</div>
<Drawer title="工作流测试">
<fieldset>
<legend class="ml-2"><h3>运行参数配置</h3></legend>
<div class="p-2">
<fieldset
class="min-inline-size-auto m-0 rounded-[6px] border border-[#dcdfe6] p-[12px_16px]"
>
<legend
class="ml-[8px] px-[10px] text-[16px] font-semibold text-[#303133]"
>
<h3>运行参数配置</h3>
</legend>
<div class="p-[8px]">
<div
class="mb-1 flex justify-around"
class="mb-[4px] flex items-center justify-around"
v-for="(param, index) in params4Test"
:key="index"
>
@ -218,7 +225,7 @@ defineExpose({ validate });
</Select.Option>
</Select>
<Input
class="w-[200px]"
class="mx-[8px] w-[200px]"
v-model:value="param.value"
placeholder="参数值"
/>
@ -228,26 +235,35 @@ defineExpose({ validate });
</template>
</Button>
</div>
<Button type="primary" plain @click="addParam"></Button>
<Button type="primary" plain class="mt-[8px]" @click="addParam">
添加参数
</Button>
</div>
</fieldset>
<fieldset class="mt-2" style="background-color: #f8f9fa">
<legend class="ml-2"><h3>运行结果</h3></legend>
<div class="p-2">
<div v-if="loading"><el-text type="primary">执行中...</el-text></div>
<div v-else-if="error">
<el-text type="danger">{{ error }}</el-text>
</div>
<pre v-else-if="testResult" class="result-content">
{{ JSON.stringify(testResult, null, 2) }}
<fieldset
class="min-inline-size-auto m-0 mt-[8px] rounded-[6px] border border-[#dcdfe6] bg-[#f8f9fa] p-[12px_16px]"
>
<legend
class="ml-[8px] px-[10px] text-[16px] font-semibold text-[#303133]"
>
<h3>运行结果</h3>
</legend>
<div class="p-[8px]">
<div v-if="loading" class="text-primary">...</div>
<div v-else-if="error" class="text-danger">{{ error }}</div>
<pre
v-else-if="testResult"
class="max-h-[300px] overflow-auto whitespace-pre-wrap rounded-[4px] bg-white p-[12px] font-mono text-[14px] leading-[1.5]"
>{{ JSON.stringify(testResult, null, 2) }}
</pre>
<div v-else style="color: #909399">点击运行查看结果</div>
<div v-else class="text-[#909399]">点击运行查看结果</div>
</div>
</fieldset>
<Button
class="mt-2"
size="large"
style="width: 100%; color: white; background-color: #67c23a"
class="mt-[8px] w-[100%] bg-[#67c23a] text-white"
@click="goRun"
>
运行流程
@ -255,32 +271,3 @@ defineExpose({ validate });
</Drawer>
</div>
</template>
<style lang="css" scoped>
.result-content {
max-height: 300px;
padding: 12px;
overflow: auto;
font-family: Monaco, Consolas, monospace;
font-size: 14px;
line-height: 1.5;
white-space: pre-wrap;
background: white;
border-radius: 4px;
}
fieldset {
min-inline-size: auto;
padding: 12px 16px;
margin: 0;
border: 1px solid #dcdfe6;
border-radius: 6px;
}
legend {
padding: 0 10px;
font-size: 16px;
font-weight: 600;
color: #303133;
}
</style>

View File

@ -52,7 +52,7 @@ watch(copied, (val) => {
});
</script>
<template>
<Card class="my-card h-full">
<Card class="my-card flex h-full flex-col">
<template #title>
<h3 class="m-0 flex shrink-0 items-center justify-between px-7">
<span>预览</span>
@ -123,9 +123,6 @@ watch(copied, (val) => {
}
.my-card {
display: flex;
flex-direction: column;
:deep(.ant-card-body) {
box-sizing: border-box;
flex-grow: 1;

View File

@ -29,4 +29,3 @@ const emits = defineEmits<{
</span>
</div>
</template>
<style scoped></style>