Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front

This commit is contained in:
X1627315083@163.com
2026-02-13 13:04:07 +08:00
25 changed files with 1082 additions and 110 deletions

View File

@@ -1,4 +1,4 @@
# VITE_APP_URL = http://192.168.31.82:8771 # VITE_APP_URL = http://192.168.31.82:8771
# VITE_APP_URL = http://18.167.251.121:10095 # VITE_APP_URL = http://18.167.251.121:10095
VITE_APP_URL = https://www.lc-api.aida.com.hk VITE_APP_URL = http://192.168.31.118:8080
VITE_GOOGLE_CLIENT_ID = 216037134725-7q8vqp0ohtmohlosltkfg7bd2v29rm5a.apps.googleusercontent.com VITE_GOOGLE_CLIENT_ID = 216037134725-7q8vqp0ohtmohlosltkfg7bd2v29rm5a.apps.googleusercontent.com

View File

@@ -1,5 +1,6 @@
<template> <template>
<RouteCache /> <!-- <RouteCache /> -->
<router-view ></router-view>
<div id="loading" v-if="loading" v-loading="true"></div> <div id="loading" v-if="loading" v-loading="true"></div>
</template> </template>

79
src/api/agent.ts Normal file
View File

@@ -0,0 +1,79 @@
import request from '@/utils/request'
// 对话
export interface AgentParamsType {
message: string // 消息
threadId: string // 对话ID
checkpointId?: string // 检查点ID
imageUrlList?: string[] // 图片URL列表
configParams: Record<string, any> // 其他配置参数
token: string
}
export const fetchAgentReply = (data: AgentParamsType): Promise<AgentResponse> => {
return request({
url: '/api/ai-design/chat',
method: 'post',
data,
meta: { responseAll: true }
})
}
// 流式对话
export const fetchAgentReplyStream = async (
data: AgentParamsType,
onMessage: (chunk: string) => void,
onEnd: () => void
) => {
try {
const params = new URLSearchParams({
message: data.message,
threadId: data.threadId,
token: data.token,
configParams: JSON.stringify(data.configParams)
})
const response = await fetch(`/api/ai-design/chat?${params.toString()}`, {
method: 'GET'
})
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const reader = response.body?.getReader()
if (!reader) {
throw new Error('Response body is not readable')
}
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n')
buffer = lines.pop() || '' // 保留不完整的行
for (const line of lines) {
console.log('line---', line)
const trimmedLine = line.trim()
if (trimmedLine.startsWith('data: ')) {
const chunk = trimmedLine.slice(6)
if (chunk === '[DONE]') {
onEnd()
return
} else {
onMessage(chunk)
}
}
}
}
onEnd()
} catch (error) {
console.error('Stream error:', error)
onEnd()
}
}

View File

@@ -29,8 +29,44 @@ body,
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.background-pink { @keyframes opacity-in {
background-color: #f8f7f5; 0% {
background-image: url('@/assets/images/home-bg.png'); opacity: 0;
background-size: 100% 100%; }
100% {
opacity: 1;
}
}
@keyframes z-index-10to-1 {
0% {
z-index: 10;
}
100% {
z-index: -1;
}
}
.flex {
display: flex;
}
.flex-center {
justify-content: center;
align-items: center;
}
.flex-1 {
flex: 1;
}
.flex-col {
flex-direction: column;
}
.align-center {
align-items: center;
}
.space-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.relative {
position: relative;
} }

View File

@@ -34,41 +34,55 @@ body,
} }
} }
.background-pink { @keyframes opacity-in {
background-color: rgba(248, 247, 245, 1); 0% {
background-image: url('@/assets/images/home-bg.png'); opacity: 0;
background-size: 100% 100%; }
100% {
opacity: 1;
}
} }
.flex{ @keyframes z-index-10to-1 {
0% {
z-index: 10;
}
100% {
z-index: -1;
}
}
.flex {
display: flex; display: flex;
} }
.flex-center{ .flex-center {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.flex-1{ .flex-1 {
flex: 1; flex: 1;
} }
.flex-col{ .flex-col {
flex-direction: column; flex-direction: column;
} }
.align-center{ .align-center {
align-items: center; align-items: center;
} }
.space-between{ .space-between {
justify-content: space-between; justify-content: space-between;
} }
.justify-center{ .justify-center {
justify-content: center; justify-content: center;
} }
.relative{ .relative {
position: relative; position: relative;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 697 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1,8 +1,9 @@
export default { export default {
AlphaVersion: '2026 Alpha Version',
Login: { Login: {
Login: 'Log in', login: 'Log in',
SignUp: 'Sign up', register: 'Register',
LoginTo: 'Log on to', loginTo: 'Log on to <span>FiDA</span>',
LoginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.', LoginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
name: 'Name', name: 'Name',
email: 'Email', email: 'Email',
@@ -21,7 +22,7 @@ export default {
agreeTermsPolicy: agreeTermsPolicy:
'I agree to the <span onclick="onClickPrivacy()">Terms, Policy</span> and Fees.', 'I agree to the <span onclick="onClickPrivacy()">Terms, Policy</span> and Fees.',
noAccountToSignUp: `Don't have an account? <span onclick="onClickRegister()">Sign up</span>`, noAccountToSignUp: `Don't have an account? <span onclick="onClickRegister()">Sign up</span>`,
registerFor: 'Register for', signUpFor: 'Sign up for <span>FiDA</span>',
registerTip: 'A multi-agent canvas for rapid, trend driven design iteration.', registerTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
havenAccountToLogin: `Already have an account? <span onclick="onClickLogin()">Log in</span>`, havenAccountToLogin: `Already have an account? <span onclick="onClickLogin()">Log in</span>`,
verifyEmail: 'Verify your email address', verifyEmail: 'Verify your email address',
@@ -31,21 +32,23 @@ export default {
resendCodeIn: 'Resend Code in {time}', resendCodeIn: 'Resend Code in {time}',
orContinueWith: 'or continue with', orContinueWith: 'or continue with',
googleLogin: 'Sign in with Google', googleLogin: 'Sign in with Google',
wechatLogin: 'Sign in with Wechat' wechatLogin: 'Sign in with Wechat',
indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
}, },
Nuic: { Nuic: {
hiName: 'Hi, {name}.', hiName: 'Hi, {name}. This is Fiphant.',
nuic1Title: `Help Fiphant discover the <b>'YOU'</b> in your space.`, nuic1Title: `Help him discover the <b>"YOU"</b> in your space.`,
nuic1Tip: `Let's set up your profile. A few quick details will help Fiphant understand<br />your needs and find exactly what you're looking for.`, nuic1Tip: `Let's set up your profile. A few quick details will help Fiphant understand<br />your needs and find exactly what you're looking for.`,
letsGo: 'Lets go, Fiphant!', letsGo: 'Lets go, Fiphant!',
skip: 'Skip', skip: 'Skip',
next: 'Next', next: 'Next',
nuic2Title: `What's your dream <b>home vibe</b> ?`, nuic2Title: `What <b>vibe</b> do you usually go for?`,
loadMore: 'Load more', loadMore: 'Load more',
nuic3Title: `Where <b>are you based</b>? What do you <b>do</b> ?`, nuic3Title: `<b>Where</b> are you based? What do you <b>do</b>?`,
basedIn: 'Based in', basedIn: 'Based in',
role: 'Role', role: 'Role',
allSet: 'All set!' allSet: 'All set!',
loadingTip: 'Were customizing your dashboard.',
}, },
Home: { Home: {
creditsNum: 'Credits: {num}', creditsNum: 'Credits: {num}',

View File

@@ -5,13 +5,13 @@ import { removeLocal, setLocal } from '@/utils/local'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
export const useUserInfoStore = defineStore('userInfo', () => { export const useUserInfoStore = defineStore('userInfo', () => {
const state = ref({ const state = ref({
userInfo: {}, userInfo: {},
token: '', token: '',
generateParams: { generateParams: {
stylist: '', stylist: '',
sex: '', sex: '',
stylistImage: '' stylistImage: ''
} }
}) })
// getters // getters

View File

@@ -1,6 +1,12 @@
<template> <template>
<div class="view-404"> <div class="view-404">
<p>404 Not Found</p> <!-- <p>404 Not Found</p> -->
<div class="demo">
<div></div>
<div></div>
<div></div>
</div>
</div> </div>
</template> </template>
@@ -10,6 +16,8 @@
<style lang="less"> <style lang="less">
.view-404 { .view-404 {
width: 100%;
height: 100%;
text-align: center; text-align: center;
> p { > p {
margin-top: 5rem; margin-top: 5rem;
@@ -17,5 +25,68 @@
font-size: 3rem; font-size: 3rem;
color: #0094ff; color: #0094ff;
} }
> .demo {
position: absolute;
width: 127rem;
height: 72.6rem;
opacity: 0.8;
left: 50%;
bottom: 0;
transform: translateX(-50%) translateY(80%);
> div {
position: absolute;
border-radius: 50%;
}
> div:nth-child(1) {
width: 48.4rem;
height: 57.2rem;
top: 2.3rem;
left: 0rem;
background: linear-gradient(
130.72deg,
rgba(242, 171, 180, 0.2) 29.52%,
rgba(234, 133, 200, 0.2) 39.73%,
rgba(238, 64, 173, 0.2) 55.81%,
rgba(234, 133, 158, 0.2) 69.59%,
rgba(242, 171, 180, 0.2) 82.61%
);
transform: matrix(-0.16, -0.99, 0.97, -0.24, 0, 0);
filter: blur(11.5rem);
}
> div:nth-child(2) {
width: 44.6rem;
height: 56.8rem;
left: 56.6rem;
top: 0rem;
background: conic-gradient(
from 94.36deg at 71.77% 41.01%,
#f2abb4 0deg,
#ff6175 100.75deg,
#fe3b53 179.32deg,
#ff6175 252deg,
#f2abb4 360deg
);
transform: matrix(0.75, -0.66, 0.65, 0.76, 0, 0);
filter: blur(12.927rem);
}
> div:nth-child(3) {
width: 79.958rem;
height: 28.1rem;
left: 11.724rem;
top: 12.353rem;
background: linear-gradient(
87.58deg,
#f1c191 23.02%,
rgba(255, 178, 91, 0.97) 35.36%,
#ff8e69 56.32%,
#f2ad7e 67.34%,
#ffe8c8 82.74%
);
transform: matrix(-1, 0.05, -0.03, -1, 0, 0);
filter: blur(13.17rem);
}
}
} }
</style> </style>

View File

@@ -26,7 +26,6 @@
import To3DModel from './to-3d-model.vue' import To3DModel from './to-3d-model.vue'
import AddPrint from './add-print.vue' import AddPrint from './add-print.vue'
import ToCAD from './to-cad.vue' import ToCAD from './to-cad.vue'
import EditMaterial from './edit-material.vue' import EditMaterial from './edit-material.vue'
const components = [ const components = [
{ {

View File

@@ -8,16 +8,21 @@
<SvgIcon name="equal" color="#0d0d0d" size="24" /> <SvgIcon name="equal" color="#0d0d0d" size="24" />
</div> </div>
<div class="agent-body flex-1 flex flex-col"> <div class="agent-body flex-1 flex flex-col">
<List /> <List ref="listRef" :message-list="messageList" />
<Input :is-agent-mode="true" @send="handleSendMessage" /> <Input is-agent-mode @send="handleSendMessage" />
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onUnmounted } from 'vue'
import List from './List.vue' import List from './List.vue'
import Input from '../../components/Input.vue' import Input from '../../components/Input.vue'
import { fetchAgentReply } from '@/api/agent'
import type { AgentParamsType } from '@/api/agent'
import { useUserInfoStore } from '@/stores'
const userStore = useUserInfoStore()
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
title: string title: string
@@ -27,8 +32,179 @@
} }
) )
const handleSendMessage = (message: string) => { const messageList = ref([])
const listRef = ref()
const params = reactive<AgentParamsType>({
threadId: '',
message: '',
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiaWF0IjoxNzcwNzkxMzEyLCJleHAiOjE3NzA4Nzc3MTJ9.xydPinm9l5Yq6GMkfaaVvdHjiINaYrp5VkRM7B9g83A',
checkpointId: '',
configParams: {
type: 'Chair',
region: 'China',
style: 'Transitional',
temperature: 0.7
},
imageUrlList: []
})
const abort = new AbortController()
onUnmounted(() => {
abort.abort()
})
const handleSendMessage = async (message: string) => {
console.log('Message sent:', message) console.log('Message sent:', message)
params.message = message.text
params.imageUrlList = message.images || []
messageList.value.push({
id: messageList.value.length + 1,
text: message.text,
isUser: true
})
// Add AI loading message
const aiMessage = reactive({
id: messageList.value.length + 1,
text: '',
isUser: false,
loading: true,
thinking: false,
thinkingText: '',
thinkingCollapsed: false,
streaming: true
})
messageList.value.push(aiMessage)
const threadId = '' //
console.log('token---', params.token, '参数---', params)
try {
const urlParams = new URLSearchParams<AgentParamsType>({
...params,
configParams: JSON.stringify(params.configParams)
})
const response = await fetch(`/api/ai-design/chat?${urlParams.toString()}`, {
method: 'GET',
signal: abort.signal
})
// 检查响应内容类型,判断是否为流式响应
const contentType = response.headers.get('content-type') || ''
const isStreamResponse =
contentType.includes('text/event-stream') || contentType.includes('stream')
if (!response.ok) {
// 非流式错误响应,使用 text() 读取错误信息
const errorText = await response.text()
console.error('请求错误:', errorText)
throw new Error(`发起对话错误--- ${response.status}: ${errorText}`)
}
// 不是流式响应,使用 text()读取错误信息
if (!isStreamResponse) {
const text = await response.text()
try {
const errorData = JSON.parse(text)
console.error('非流式响应:', errorData)
} catch (e) {
console.error('非流式响应文本:', text)
}
aiMessage.streaming = false
aiMessage.loading = false
return
}
// 流式响应处理
let contentBody = ''
let buffer = ''
const reader = response.body?.getReader()
if (!reader) throw new Error('无法获取流读取器')
const decoder = new TextDecoder()
try {
let flag = true
while (flag) {
const { done, value } = await reader.read()
if (done) {
console.log('传输结束 end---', contentBody)
aiMessage.streaming = false
aiMessage.loading = false
flag = false
break
}
buffer += decoder.decode(value, { stream: true })
// 优先按空行拆分事件块SSE标准
let events = buffer.split(/\n\n/)
buffer = events.pop() // 保留不完整块
for (let event of events) {
if (!event.trim()) continue
// 过滤掉 id: 等字段,只取 data:
const dataLines = event
.split(/\n/)
.filter((line) => line.startsWith('data:'))
.map((line) => line.replace(/^data:\s*/, '').trim())
if (dataLines.length === 0) continue
const jsonText = dataLines.join('\n')
try {
const jsonData = JSON.parse(jsonText)
// 赋值 thread_id 和 checkpoint_id
if (jsonData.thread_id) params.threadId = jsonData.thread_id
if (jsonData.checkpoint_id) params.checkpointId = jsonData.checkpoint_id
if (
jsonData.content &&
jsonData.content.length > 0 &&
jsonData.type !== 'end'
) {
contentBody += jsonData.content
aiMessage.text = contentBody
}
if (jsonData.type === 'end') {
aiMessage.streaming = false
aiMessage.loading = false
flag = false
break
}
} catch (e) {
// 检查是否为纯文本 [DONE]
if (jsonText.trim() === '[DONE]') {
console.log('结束-----------------------')
aiMessage.streaming = false
aiMessage.loading = false
flag = false
break
}
// JSON 不完整:保留到下一次循环
if (!jsonText.trim().endsWith('}')) {
buffer = 'data: ' + jsonText // 重新放回缓存
continue
} else {
console.warn('⚠️ JSON 格式错误,跳过:', jsonText)
}
}
}
}
} catch (error) {
console.error('流式传输错误:', error)
aiMessage.streaming = false
aiMessage.loading = false
} finally {
reader.releaseLock()
}
} catch (error) {
console.error('fetch请求失败:', error)
aiMessage.streaming = false
aiMessage.loading = false
}
} }
</script> </script>
@@ -60,6 +236,8 @@
} }
.agent-body { .agent-body {
padding: 3.2rem; padding: 3.2rem;
overflow: hidden;
row-gap: 2.4rem;
.assist-input-wrapper { .assist-input-wrapper {
width: 100%; width: 100%;
height: 14.4rem; height: 14.4rem;

View File

@@ -1,11 +1,16 @@
<template> <template>
<div class="agent-item"> <div class="agent-item">
<div class="message-wrapper flex align-center" :class="{ 'is-user': content.isUser }"> <div class="message-wrapper flex" :class="{ 'is-user': content.isUser }">
<div class="thumb"> <div class="thumb">
<img :src="content.isUser ? userThumb : agentThumb" class="thumb-icon" /> <img :src="content.isUser ? userThumb : agentThumb" class="thumb-icon" />
</div> </div>
<div class="message-context"> <div
<div class="message-txt">{{ content.text }}</div> class="message-context"
v-show="!content.loading && !content.thinking && !content.streaming"
>
<div class="message-txt markdown-body">
<div v-html="formatMessage"></div>
</div>
<div class="operate flex" :class="{ 'is-user': content.isUser }"> <div class="operate flex" :class="{ 'is-user': content.isUser }">
<template v-if="content.isUser"> <template v-if="content.isUser">
<SvgIcon name="copy" size="16" color="#000" @click.stop="handleCopyText" /> <SvgIcon name="copy" size="16" color="#000" @click.stop="handleCopyText" />
@@ -22,15 +27,38 @@
</template> </template>
</div> </div>
</div> </div>
<div class="message-context" v-show="content.loading">
<div class="generating">Generating...</div>
</div>
<div class="message-context" v-show="content.thinking">
<div class="thinking">
<div class="thinking-header flex align-center" @click="toggleThinkingCollapsed">
<span>思考中</span>
<!-- <SvgIcon :name="content.thinkingCollapsed ? 'arrowDown' : 'arrowUp'" size="16" color="#666" /> -->
</div>
<div class="thinking-content" v-show="!content.thinkingCollapsed">
<pre>{{ content.thinkingText }}</pre>
</div>
</div>
</div>
<div class="message-context" v-show="content.streaming">
<div class="message-txt">
<div v-html="formatMessage"></div>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import gsap from 'gsap'
import userThumb from '@/assets/images/user-thumb.jpg' import userThumb from '@/assets/images/user-thumb.jpg'
import agentThumb from '@/assets/images/agent-thumb.jpg' import agentThumb from '@/assets/images/agent-thumb.jpg'
import markdownIt from 'markdown-it'
const md = new markdownIt()
const { t } = useI18n() const { t } = useI18n()
@@ -38,6 +66,21 @@
content: Object content: Object
}>() }>()
const formatMessage = computed(() => {
// MARKDOWN.renderer.rules.link_open = (tokens, idx, options, env, self) => {
// const aIndex = tokens[idx].attrIndex('target')
// if (aIndex < 0) {
// tokens[idx].attrPush(['target', '_blank'])
// } else {
// tokens[idx].attrs[aIndex][1] = '_blank'
// }
// return self.renderToken(tokens, idx, options, env, self)
// }
const str = md.render(props.content.text)
console.log('str',str)
return str
})
const operateList = ref([ const operateList = ref([
{ {
name: 'thumbUp', name: 'thumbUp',
@@ -65,6 +108,8 @@
} }
]) ])
const loading = ref(false)
const handleCopyText = () => { const handleCopyText = () => {
navigator.clipboard navigator.clipboard
.writeText(props.content.text) .writeText(props.content.text)
@@ -85,6 +130,10 @@
}) })
}) })
} }
const toggleThinkingCollapsed = () => {
props.content.thinkingCollapsed = !props.content.thinkingCollapsed
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -97,6 +146,7 @@
font-size: 1.4rem; font-size: 1.4rem;
.message-wrapper { .message-wrapper {
column-gap: 0.9rem; column-gap: 0.9rem;
// align-items: flex-start;
&.is-user { &.is-user {
text-align: right; text-align: right;
flex-direction: row-reverse; flex-direction: row-reverse;
@@ -115,6 +165,10 @@
border-radius: 50%; border-radius: 50%;
} }
} }
.message-context{
line-height: 2rem;
font-size: 1.4rem;
}
} }
.operate { .operate {
margin-top: 1.3rem; margin-top: 1.3rem;
@@ -124,4 +178,38 @@
} }
} }
} }
.generating {
font-family: 'GeneralBold';
font-weight: 600;
font-size: 1.55rem;
background: linear-gradient(45deg, #f2ab4a, #ff6b75, #fe3b55);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-fill-color: transparent;
}
.thinking {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
background-color: #f9f9f9;
.thinking-header {
cursor: pointer;
font-weight: bold;
justify-content: space-between;
}
.thinking-content {
margin-top: 0.5rem;
pre {
white-space: pre-wrap;
font-family: inherit;
font-size: 1.2rem;
color: #666;
}
}
}
</style> </style>

View File

@@ -1,29 +1,41 @@
<template> <template>
<div class="agent-list flex flex-col flex-1"> <div class="agent-list flex flex-col flex-1" ref="listContainer">
<Item v-for="message in messageList" :key="message.id" :content="message" /> <Item v-for="message in messageList" :key="message.id" :content="message" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref, nextTick, watch } from 'vue'
import Item from './Item.vue' import Item from './Item.vue'
const messageList = ref([ const props = defineProps<{
{ id: 1, text: 'Hello', isUser: true }, messageList: Array<any>
{ }>()
id: 2,
text: 'Hey, I am your design assistant FiDA. I noticed that you want to design a yellow sofa. I can help you! Tell me what else you need?' const listContainer = ref<HTMLDivElement>()
},
{ const scrollToBottom = () => {
id: 3, nextTick(() => {
text: 'Please design a vintage-inspired sofa with smooth, flowing lines and a sculptural silhouette. The sofa features a retro aesthetic combined with elegant curves, creating a timeless and refined look.', if (listContainer.value) {
isUser: true listContainer.value.scrollTop = listContainer.value.scrollHeight
} }
]) })
}
watch(() => props.messageList, () => {
scrollToBottom()
}, { deep: true })
defineExpose({ scrollToBottom })
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.agent-list { .agent-list {
row-gap: 3.2rem; row-gap: 3.2rem;
overflow-y: auto;
// 隐藏滚动条
&::-webkit-scrollbar {
display: none;
}
} }
</style> </style>

View File

@@ -0,0 +1,22 @@
<template>
<div class="menu-wrapper flex space-between">
<div class="menu-item"></div>
<div class="menu-item"></div>
<div class="menu-item"></div>
</div>
</template>
<script setup></script>
<style lang="less" scoped>
.menu-wrapper {
width: 1.8rem;
cursor: pointer;
.menu-item {
width: 0.4rem;
height: 0.4rem;
background-color: #000;
border-radius: 50%;
}
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div
class="preview-container flex"
:class="type === 'sketch' ? 'sketch-preview' : 'report-preview'"
>
<template v-if="type === 'sketch'">
<div class="sketch-item" v-for="item in 12">
<Menu class="menu-btn" @click="handleClickMenu" />
<div class="edit-btn flex align-center space-between" @click="handleClickEdit">
<div>Edit</div>
<img src="@/assets/images/arrow-top-right.png" alt="" />
</div>
<img :src="LoadingImg" alt="" />
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Menu from './Menu.vue'
import LoadingImg from '@/assets/images/sketch-loading.gif'
const props = withDefaults(
defineProps<{
type: 'sketch' | 'report'
}>(),
{
type: 'sketch'
}
)
const handleClickEdit = () => {
// 编辑按钮点击逻辑
console.log('Edit button clicked')
}
const handleClickMenu = () => {
// 菜单按钮点击逻辑
console.log('Menu button clicked')
}
</script>
<style lang="less" scoped>
.preview-container {
gap: 1.2rem;
flex-wrap: wrap;
.sketch-item {
position: relative;
&,
img {
width: 21.9rem;
height: 21.9rem;
border-radius: 1.6rem;
}
.menu-btn {
position: absolute;
top: 2.1rem;
right: 1.5rem;
}
.edit-btn {
position: absolute;
right: 1rem;
bottom: 1.1rem;
width: 7.8rem;
height: 3.59rem;
border-radius: 2rem;
background-color: #fff;
border: 0.2rem solid #e5e5e5;
font-family: 'GeneralMedium';
font-size: 1.4rem;
padding: 0 0.9rem 0 1.4rem;
cursor: pointer;
img{
width: 1.8rem;
height: 1.8rem;
}
}
}
}
</style>

View File

@@ -1,14 +1,19 @@
<template> <template>
<div class="agent-wrapper flex space-between"> <div class="agent-wrapper flex space-between">
<Agent :title="agentTitle" /> <Agent :title="agentTitle" />
<div class="preview-wrapper">preview</div> <div class="preview-wrapper">
<Preview :type="previewType" />
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue' import { ref } from 'vue'
import Agent from './components/Agent.vue' import Agent from './components/Agent.vue'
import Preview from './components/Preview.vue'
const agentTitle = ref('Retro Sofa Sketch') const agentTitle = ref('Retro Sofa Sketch')
const previewType = ref<'sketch' | 'report'>('sketch')
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -24,7 +29,6 @@
.preview-wrapper { .preview-wrapper {
width: 91.2rem; width: 91.2rem;
background-color: #f5f5f5;
} }
} }
</style> </style>

View File

@@ -22,6 +22,7 @@
:placeholder="$t('Input.placeholder')" :placeholder="$t('Input.placeholder')"
@input="handleEditorInput" @input="handleEditorInput"
@paste="handleEditorPaste" @paste="handleEditorPaste"
@keypress="handleKeyPress"
> >
<!-- <Tag v-if="showReportTag" /> --> <!-- <Tag v-if="showReportTag" /> -->
<div <div
@@ -188,7 +189,7 @@
} }
) )
const emits = defineEmits(['send']) const emits = defineEmits(['send'])
const { t } = useI18n() const { t } = useI18n()
@@ -295,14 +296,23 @@
} }
} }
const handleSendAgent=()=>{ const handleKeyPress = (e) => {
emits('send', inputValue.value) // 检测回车
// 发送后清空输入框 if (e.key === 'Enter' && !e.shiftKey) {
if(editorRef.value){ e.preventDefault()
editorRef.value.innerHTML = '' handleSendAgent()
} }
inputValue.value = '' }
}
const handleSendAgent = () => {
if (!inputValue.value.trim()) return
emits('send', { text: inputValue.value.trim(), images: uploadedImages.value })
// 发送后清空输入框
if (editorRef.value) {
editorRef.value.innerHTML = ''
}
inputValue.value = ''
}
// 监听 inputValue 外部变化 // 监听 inputValue 外部变化
watch(inputValue, () => { watch(inputValue, () => {
nextTick(() => { nextTick(() => {
@@ -314,7 +324,6 @@
onMounted(() => { onMounted(() => {
autoResizeEditor() autoResizeEditor()
}) })
const typeValue = ref<string>('') const typeValue = ref<string>('')
const areaValue = ref<string>('') const areaValue = ref<string>('')
@@ -598,7 +607,7 @@
.agent { .agent {
padding: 1.2rem; padding: 1.2rem;
box-shadow: none; box-shadow: none;
border-radius: 1.5rem; border-radius: 1.5rem;
border: 0.1rem solid #0000001a; border: 0.1rem solid #0000001a;
.scroll-content { .scroll-content {
padding: 0; padding: 0;
@@ -611,7 +620,7 @@
min-height: initial; min-height: initial;
max-height: initial; max-height: initial;
padding: 0; padding: 0;
height: 100%; height: 100%;
} }
} }
.operate { .operate {

View File

@@ -1,5 +1,14 @@
<template> <template>
<div class="home background-pink"> <div class="bg bg-1">
<div class="topright"></div>
<div class="bottomleft"></div>
</div>
<div class="bg bg-2">
<div class="bottom-1"></div>
<div class="bottom-2"></div>
<div class="bottom-3"></div>
</div>
<div class="home">
<left-nav /> <left-nav />
<div class="right-main"> <div class="right-main">
<top-nav /> <top-nav />
@@ -38,4 +47,144 @@
} }
} }
} }
.bg-1 {
z-index: -1;
animation: opacity-in 0.5s ease-in-out 1 both;
}
.bg-2 {
animation: z-index-10to-1 0.5s ease-in-out 1 both;
}
.bg {
position: absolute;
width: 100%;
height: 100%;
overflow: hidden;
// background-color: rgba(248, 247, 245, 1);
> * {
position: absolute;
border-radius: 50%;
}
> .topright {
width: 174.7rem;
height: 42.1rem;
top: -15.7rem;
right: -70rem;
background: linear-gradient(
86.85deg,
rgba(244, 147, 116, 0.6) 13.62%,
rgba(241, 193, 145, 0.6) 25.57%,
rgba(255, 178, 91, 0.582) 41.03%,
rgba(242, 173, 126, 0.6) 59.37%,
rgba(255, 232, 200, 0.6) 75.27%
);
opacity: 0.2;
filter: blur(20.7656rem);
transform: rotate(178.95deg);
}
> .bottomleft {
// border-radius: 0;
// border: 1px solid #000;
width: 62.418rem;
height: 68.245rem;
left: -30rem;
bottom: 0;
background: linear-gradient(
161.16deg,
rgba(132, 230, 255, 0.2) 14.98%,
rgba(255, 223, 142, 0.2) 68.79%
);
filter: blur(8.263rem);
transform: rotate(-25.36deg);
}
> .bottom-1 {
animation: bottom-1 0.5s ease-in-out 1 both;
background: linear-gradient(
87.58deg,
rgba(241, 193, 145, 0.8) 23.02%,
rgba(255, 178, 91, 0.776) 35.36%,
rgba(244, 147, 116, 0.8) 56.32%,
rgba(242, 173, 126, 0.8) 67.34%,
rgba(255, 232, 200, 0.8) 82.74%
);
filter: blur(13.17rem);
transform: matrix(-1, 0.03, -0.05, -1, 0, 0);
}
> .bottom-2 {
animation: bottom-2 0.5s ease-in-out 1 both;
background: conic-gradient(
from 94.36deg at 71.77% 41.01%,
rgba(242, 171, 180, 0.2) 0deg,
rgba(255, 105, 117, 0.2) 100.75deg,
rgba(254, 59, 83, 0.2) 179.32deg,
rgba(255, 105, 117, 0.2) 252deg,
rgba(242, 171, 180, 0.2) 360deg
);
filter: blur(12.927rem);
transform: matrix(-0.05, 1, -1, -0.03, 0, 0);
}
> .bottom-3 {
animation: bottom-3 0.5s ease-in-out 1 both;
background: linear-gradient(
130.72deg,
rgba(242, 171, 180, 0.24) 29.52%,
rgba(234, 133, 200, 0.24) 39.73%,
rgba(238, 64, 173, 0.24) 55.81%,
rgba(234, 133, 158, 0.24) 69.59%,
rgba(242, 171, 180, 0.24) 82.61%
);
filter: blur(11.5411rem);
transform: matrix(-0.26, -0.97, 0.99, -0.15, 0, 0);
}
@keyframes bottom-1 {
0% {
width: 15rem;
height: 15rem;
left: 50%;
bottom: 50%;
transform: translate(0, 50%);
}
100% {
width: 138.014rem;
height: 29.323rem;
left: 32.123rem;
bottom: -21rem;
transform: translate(0, 0);
}
}
@keyframes bottom-2 {
0% {
width: 15rem;
height: 15rem;
left: 50%;
bottom: 50%;
transform: translate(0, 50%);
}
100% {
width: 42.215rem;
height: 98.009rem;
left: 150rem;
bottom: -65rem;
transform: translate(0, 0);
}
}
@keyframes bottom-3 {
0% {
width: 15rem;
height: 15rem;
left: 50%;
bottom: 50%;
transform: translate(0, 50%);
}
100% {
width: 51.936rem;
height: 97.139rem;
left: 40rem;
bottom: -65rem;
transform: translate(0, 0);
}
}
}
</style> </style>

View File

@@ -5,7 +5,7 @@
<img src="@/assets/images/logo-1.png" class="logo" /> <img src="@/assets/images/logo-1.png" class="logo" />
<p class="split"></p> <p class="split"></p>
<button class="login" @click="onLogin">{{ $t('Login.login') }}</button> <button class="login" @click="onLogin">{{ $t('Login.login') }}</button>
<button class="register" @click="onRegister">{{ $t('Login.signUp') }}</button> <button class="register" @click="onRegister">{{ $t('Login.register') }}</button>
</div> </div>
<img src="@/assets/images/login/index-title.png" class="title" draggable="false" /> <img src="@/assets/images/login/index-title.png" class="title" draggable="false" />
<img src="@/assets/images/login/index-zhuangshi.png" class="zhuangshi" draggable="false" /> <img src="@/assets/images/login/index-zhuangshi.png" class="zhuangshi" draggable="false" />

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="nuic background-pink"> <div class="nuic">
<img class="logo" src="@/assets/images/logo-2.png" /> <img class="logo" src="@/assets/images/logo-2.png" />
<div class="header" v-show="!loading"> <div class="header" :class="{ loading }">
<div class="close" @click="onClose"> <div class="close" @click="onClose">
<svg-icon name="close-border" size="33" /> <svg-icon name="close-border" size="33" />
</div> </div>
@@ -19,11 +19,14 @@
/> />
</div> </div>
</div> </div>
<component v-show="!loading" class="view" :is="list[active]" @next="onNext" /> <component :class="{ loading }" class="view" :is="list[active]" @next="onNext" />
<div class="loading" v-show="loading"> <div class="bg" :class="{ loading }">
<img src="@/assets/images/nuic/loading.png" /> <div class="bg-3"></div>
<p class="tip">{{ $t('Nuic.loadingTip') }}</p> <div class="bg-2"></div>
<div class="bg-1"></div>
</div> </div>
<img class="loading-img" :class="{ loading }" src="@/assets/images/nuic/xiang.png" />
<div class="loading-tip" :class="{ loading }">{{ $t('Nuic.loadingTip') }}</div>
</div> </div>
</template> </template>
@@ -49,7 +52,7 @@
loading.value = true loading.value = true
setTimeout(() => { setTimeout(() => {
router.push({ name: 'mainInput' }) router.push({ name: 'mainInput' })
}, 1000) }, 5000)
} }
} }
</script> </script>
@@ -62,8 +65,20 @@
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
// background-color: #f8f7f5;
// align-items: center; // align-items: center;
// justify-content: center; // justify-content: center;
> * {
transition: opacity 0.5s ease-in-out;
}
> *:not(.bg):not(.loading-img):not(.loading-tip) {
z-index: 10;
opacity: 1;
&.loading {
opacity: 0;
z-index: -1;
}
}
> .logo { > .logo {
position: absolute; position: absolute;
top: 2.7rem; top: 2.7rem;
@@ -147,18 +162,228 @@
} }
} }
} }
> .loading { > .loading-img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 14.4rem;
height: auto;
opacity: 0;
&.loading {
opacity: 1;
}
}
> .loading-tip {
position: absolute;
width: 100%; width: 100%;
height: 100%; left: 0;
display: flex; bottom: 25.2rem;
flex-direction: column; text-align: center;
align-items: center; font-family: Regular;
justify-content: center; font-size: 3rem;
background-color: #f8f7f5; color: #585858;
> .tip {
font-family: Regular; opacity: 0;
font-size: 3rem; &.loading {
color: #585858; opacity: 1;
}
}
> .bg {
position: absolute;
// border: 1px solid #000;
&,
& > * {
transition: all 0.5s ease-in-out;
animation-delay: 0.5s;
animation-duration: 6s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
> div {
position: absolute;
border-radius: 50%;
}
> div.bg-1 {
background: linear-gradient(
130.72deg,
rgba(242, 171, 180, 0.2) 29.52%,
rgba(234, 133, 200, 0.2) 39.73%,
rgba(238, 64, 173, 0.2) 55.81%,
rgba(234, 133, 158, 0.2) 69.59%,
rgba(242, 171, 180, 0.2) 82.61%
);
}
> div.bg-2 {
background: conic-gradient(
from 94.36deg at 71.77% 41.01%,
rgba(242, 171, 180, 0.2) 0deg,
rgba(255, 97, 117, 0.2) 100.75deg,
rgba(254, 59, 83, 0.2) 179.32deg,
rgba(255, 97, 117, 0.2) 252deg,
rgba(242, 171, 180, 0.2) 360deg
);
}
> div.bg-3 {
background: linear-gradient(
87.58deg,
#f1c191 23.02%,
rgba(255, 178, 91, 0.97) 35.36%,
#ff8e69 56.32%,
#f2ad7e 67.34%,
#ffe8c8 82.74%
);
}
}
> .bg.loading {
width: 47.873rem;
height: 46.704rem;
left: 50%;
top: 50%;
transform: translateX(-50%) translateY(-50%);
animation-name: bg;
> img {
opacity: 1;
}
> div.bg-1 {
animation-name: bg_1;
width: 34.164rem;
height: 34.92rem;
left: 2rem;
top: 5rem;
filter: blur(11.5rem);
transform: matrix(-0.16, -0.99, 0.97, -0.24, 0, 0);
}
> div.bg-2 {
animation-name: bg_2;
width: 25.8rem;
height: 25.8rem;
left: 17rem;
top: 4rem;
filter: blur(5.48507rem);
transform: matrix(0.4, -0.92, 0.91, 0.42, 0, 0);
}
> div.bg-3 {
animation-name: bg_3;
width: 25.8rem;
height: 25.8rem;
left: 10.4rem;
top: 17.7rem;
filter: blur(5.48507rem);
transform: matrix(-0.97, -0.23, 0.25, -0.97, 0, 0);
}
}
> .bg:not(.loading) {
width: 127rem;
height: 72.6rem;
left: 50%;
bottom: 0;
transform: translateX(-50%) translateY(80%);
> div.bg-1 {
width: 48.4rem;
height: 57.2rem;
top: 2.3rem;
left: 0rem;
transform: matrix(-0.16, -0.99, 0.97, -0.24, 0, 0);
filter: blur(11.5rem);
}
> div.bg-2 {
width: 44.6rem;
height: 56.8rem;
left: 56.6rem;
top: 0rem;
transform: matrix(0.75, -0.66, 0.65, 0.76, 0, 0);
filter: blur(12.927rem);
}
> div.bg-3 {
width: 79.958rem;
height: 28.1rem;
left: 11.724rem;
top: 12.353rem;
transform: matrix(-1, 0.05, -0.03, -1, 0, 0);
filter: blur(13.17rem);
}
}
@keyframes bg {
15% {
width: 56.647rem;
height: 51.794rem;
}
50% {
width: 46.179rem;
height: 36.712rem;
}
75% {
width: 52.947rem;
height: 46.894rem;
}
}
@keyframes bg_1 {
15% {
width: 34.2rem;
height: 34.9rem;
left: 3rem;
top: 12.7rem;
}
50% {
width: 27.321rem;
height: 27.926rem;
left: 16.7rem;
top: 3.6rem;
}
75% {
width: 34.164rem;
height: 34.92rem;
left: 2.5rem;
top: 8rem;
}
}
@keyframes bg_2 {
15% {
width: 25.8rem;
height: 25.8rem;
left: 25.5rem;
top: 5rem;
transform: matrix(0.75, -0.66, 0.65, 0.76, 0, 0);
}
50% {
width: 25.8rem;
height: 25.8rem;
left: 4rem;
top: 4rem;
transform: matrix(0.75, -0.66, 0.65, 0.76, 0, 0);
}
75% {
width: 25.8rem;
height: 25.8rem;
left: 21.8rem;
top: 5rem;
transform: matrix(0.75, -0.66, 0.65, 0.76, 0, 0);
}
}
@keyframes bg_3 {
15% {
width: 25.8rem;
height: 25.8rem;
left: 14.603rem;
top: 11.784rem;
transform: matrix(-1, 0.05, -0.03, -1, 0, 0);
}
50% {
width: 25.8rem;
height: 25.8rem;
left: 10.701rem;
top: 5.283rem;
transform: matrix(-1, 0.05, -0.03, -1, 0, 0);
}
75% {
width: 25.8rem;
height: 25.8rem;
left: 12.303rem;
top: 6.884rem;
transform: matrix(-1, 0.05, -0.03, -1, 0, 0);
} }
} }
} }

View File

@@ -5,7 +5,7 @@
<div v-for="v in list" :key="v.id" @click="v.active = !v.active"> <div v-for="v in list" :key="v.id" @click="v.active = !v.active">
<img :src="v.url" draggable="false" /> <img :src="v.url" draggable="false" />
<div class="active" v-show="v.active"> <div class="active" v-show="v.active">
<span>这是一段文字</span> <span>{{ v.title }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -25,14 +25,14 @@
const router = useRouter() const router = useRouter()
const emit = defineEmits(['next']) const emit = defineEmits(['next'])
const list = ref([ const list = ref([
{ id: 1, url: '/image/nuic/style-1.png', active: false }, { id: 1, url: '/image/nuic/style-1.png', title: '凳子', active: false },
{ id: 2, url: '/image/nuic/style-2.png', active: false }, { id: 2, url: '/image/nuic/style-2.png', title: '沙发', active: false },
{ id: 3, url: '/image/nuic/style-3.png', active: false }, { id: 3, url: '/image/nuic/style-3.png', title: '凳子', active: false },
{ id: 4, url: '/image/nuic/style-4.png', active: false }, { id: 4, url: '/image/nuic/style-4.png', title: '桌子', active: false },
{ id: 5, url: '/image/nuic/style-5.png', active: false }, { id: 5, url: '/image/nuic/style-5.png', title: '桌子', active: false },
{ id: 6, url: '/image/nuic/style-6.png', active: false }, { id: 6, url: '/image/nuic/style-6.png', title: '桌子', active: false },
{ id: 7, url: '/image/nuic/style-7.png', active: false }, { id: 7, url: '/image/nuic/style-7.png', title: '沙发', active: false },
{ id: 8, url: '/image/nuic/style-8.png', active: false } { id: 8, url: '/image/nuic/style-8.png', title: '桌子', active: false }
]) ])
const onLoadMore = () => {} const onLoadMore = () => {}
</script> </script>

View File

@@ -77,17 +77,16 @@
color: #252727; color: #252727;
font-family: LBold; font-family: LBold;
} }
> .el-select { &:deep(> .el-select) {
width: 100%; width: 100%;
--el-border-radius-base: 0.8rem; --el-border-radius-base: 0.8rem;
&:deep {
font-family: Regular; font-family: Regular;
.el-select__wrapper { .el-select__wrapper {
min-height: auto; min-height: auto;
height: 6rem; height: 6rem;
font-size: 2rem; font-size: 2rem;
padding: 0 1.8rem; padding: 0 1.8rem;
}
} }
} }
} }