This commit is contained in:
2026-02-06 09:43:10 +08:00
80 changed files with 1317 additions and 349 deletions

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"tabWidth": 4,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none"

View File

@@ -7,7 +7,7 @@
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0"> -->
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> -->
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<link rel="stylesheet" href="/css/woff/fontFamily.css">
<link rel="stylesheet" href="/fonts/general/css/general-sans.css">
<title>FiDA</title>
</head>

9
package-lock.json generated
View File

@@ -40,7 +40,6 @@
"less": "^4.3.0",
"lint-staged": "^13.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"typescript": "~4.8.4",
"unplugin-auto-import": "^0.15.3",
"unplugin-vue-components": "^0.24.1",
@@ -6256,11 +6255,16 @@
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-linter-helpers": {
@@ -13280,7 +13284,8 @@
"version": "2.8.8",
"resolved": "https://registry.npmmirror.com/prettier/-/prettier-2.8.8.tgz",
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true
"dev": true,
"peer": true
},
"prettier-linter-helpers": {
"version": "1.0.0",

View File

@@ -44,7 +44,6 @@
"less": "^4.3.0",
"lint-staged": "^13.2.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"typescript": "~4.8.4",
"unplugin-auto-import": "^0.15.3",
"unplugin-vue-components": "^0.24.1",

View File

@@ -1,50 +0,0 @@
/* cyrillic-ext */
@font-face {
font-family: 'satoshiRegular';
font-style: italic;
font-weight: 700;
src: url("./Satoshi/Satoshi-Regular.ttf") format('woff2'), url("./Satoshi/Satoshi-Regular.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'satoshiBold';
font-style: italic;
font-weight: 700;
src: url("./Satoshi/Satoshi-Bold.ttf") format('woff2'), url("./Satoshi/Satoshi-Bold.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'satoshiMedium';
font-style: italic;
font-weight: 700;
src: url("./Satoshi/Satoshi-Medium.ttf") format('woff2'), url("./Satoshi/Satoshi-Medium.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'mazzardHRegular';
font-style: italic;
font-weight: 700;
src: url("./Mazzard/MazzardH-Regular.otf") format('opentype');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'robotoBold';
font-style: italic;
font-weight: 700;
src: url("./Roboto/Roboto-Bold.ttf") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'robotoRegular';
font-style: italic;
font-weight: 700;
src: url("./Roboto/Roboto-Regular.ttf") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face {
font-family: 'boskaRegular';
font-style: italic;
font-weight: 700;
src: url("./Boska/Boska-Regular.ttf") format('woff2'), url("./Boska/Boska-Regular.woff") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}

View File

@@ -0,0 +1,70 @@
/**
* @license
*
* Font Family: General Sans
* Designed by: Frode Helland
* URL: https://www.fontshare.com/fonts/general-sans
* © 2026 Indian Type Foundry
*
* General Sans Extralight
* General Sans ExtralightItalic
* General Sans Light
* General Sans LightItalic
* General Sans Regular
* General Sans Italic
* General Sans Medium
* General Sans MediumItalic
* General Sans Semibold
* General Sans SemiboldItalic
* General Sans Bold
* General Sans BoldItalic
* General Sans Variable (Variable font)
* General Sans VariableItalic (Variable font)
*
*/
@font-face {
font-family: 'Regular';
src:url('../fonts/GeneralSans-Regular.otf') format('opentype');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Medium';
src:url('../fonts/GeneralSans-Medium.otf') format('opentype'),
url('../fonts/GeneralSans-MediumItalic.otf') format('opentype');
font-weight: 500;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'SemiBold';
src:url('../fonts/GeneralSans-Semibold.otf') format('opentype'),
url('../fonts/GeneralSans-SemiboldItalic.otf') format('opentype');
font-weight: 600;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Bold';
src:url('../fonts/GeneralSans-Bold.otf') format('opentype'),
url('../fonts/GeneralSans-BoldItalic.otf') format('opentype');
font-weight: 700;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Migra-Extrabold';
src:url('../fonts/Migra-Extrabold.otf') format('opentype'),
url('../fonts/Migra-Extrabold.ttf') format('truetype'),
url('../fonts/Migra-Extrabold.woff') format('woff'),
url('../fonts/Migra-Extrabold.woff2') format('woff2');
font-weight: 700;
font-display: swap;
font-style: normal;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -5,10 +5,22 @@
<script setup lang="ts">
import RouteCache from '@/components/RouteCache.vue'
import { useRouter } from 'vue-router'
import { computed } from 'vue'
import { useGlobalStore } from '@/stores'
const router = useRouter()
const globalStore = useGlobalStore()
const loading = computed(() => globalStore.state.loading || globalStore.state.view_loading)
window['onClickPrivacy'] = () => {
// window.event?.preventDefault()
console.log('onClickPrivacy')
}
window['onClickLogin'] = () => {
router.push({ name: 'login' })
}
window['onClickRegister'] = () => {
router.push({ name: 'register' })
}
</script>
<style lang="less">

View File

@@ -19,6 +19,7 @@ body,
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Medium';
}
@keyframes loading {
0% {
@@ -33,32 +34,3 @@ body,
background-image: url('@/assets/images/home-bg.png');
background-size: 100% 100%;
}
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.flex-center {
justify-content: center;
align-items: center;
}
.flex-1 {
flex: 1;
}
.align-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.space-between {
justify-content: space-between;
}

View File

@@ -21,6 +21,7 @@ body,
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Medium';
}
@keyframes loading {
@@ -37,4 +38,8 @@ body,
background-color: rgba(248, 247, 245, 1);
background-image: url('@/assets/images/home-bg.png');
background-size: 100% 100%;
}
.flex{
display: flex;
}

View File

@@ -0,0 +1,4 @@
<svg width="34" height="34" viewBox="0 0 34 34" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.4882 10.4882C11.139 9.83728 12.1943 9.83728 12.8452 10.4882L16.6667 14.3096L20.4882 10.4882C21.139 9.83728 22.1943 9.83728 22.8452 10.4882C23.4961 11.139 23.4961 12.1943 22.8452 12.8452L19.0237 16.6667L22.8452 20.4882C23.4961 21.139 23.4961 22.1943 22.8452 22.8452C22.1943 23.4961 21.139 23.4961 20.4882 22.8452L16.6667 19.0237L12.8452 22.8452C12.1943 23.4961 11.139 23.4961 10.4882 22.8452C9.83728 22.1943 9.83728 21.139 10.4882 20.4882L14.3096 16.6667L10.4882 12.8452C9.83728 12.1943 9.83728 11.139 10.4882 10.4882Z" fill="#666666"/>
<path d="M16.6667 3.33333C9.30287 3.33333 3.33333 9.30287 3.33333 16.6667C3.33333 24.0305 9.30287 30 16.6667 30C24.0305 30 30 24.0305 30 16.6667C30 9.30287 24.0305 3.33333 16.6667 3.33333ZM0 16.6667C0 7.46192 7.46192 0 16.6667 0C25.8714 0 33.3333 7.46192 33.3333 16.6667C33.3333 25.8714 25.8714 33.3333 16.6667 33.3333C7.46192 33.3333 0 25.8714 0 16.6667Z" fill="#666666"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.55859 12C2.55859 6.75329 6.81189 2.5 12.0586 2.5C14.5738 2.5 16.8599 3.47759 18.5586 5.07197V3.5C18.5586 2.94772 19.0063 2.5 19.5586 2.5C20.1109 2.5 20.5586 2.94772 20.5586 3.5V7.97591C20.559 7.99622 20.5588 8.01651 20.5579 8.03674C20.5386 8.572 20.0986 9 19.5586 9H15.0586C14.5063 9 14.0586 8.55228 14.0586 8C14.0586 7.44772 14.5063 7 15.0586 7H17.6491C16.275 5.46469 14.279 4.5 12.0586 4.5C7.91646 4.5 4.55859 7.85786 4.55859 12C4.55859 16.1421 7.91646 19.5 12.0586 19.5C15.9015 19.5 19.0706 16.6089 19.5072 12.8836C19.5715 12.3351 20.0683 11.9425 20.6168 12.0068C21.1653 12.0711 21.5579 12.5679 21.4936 13.1164C20.9403 17.8375 16.9279 21.5 12.0586 21.5C6.81189 21.5 2.55859 17.2467 2.55859 12Z" fill="#252727"/>
</svg>

After

Width:  |  Height:  |  Size: 830 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

View File

@@ -1,5 +1,62 @@
export default {
Login: {},
Login: {
login: 'Log in',
register: 'Register',
signUp: 'Sign up',
loginTo: 'Log on to',
loginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
name: 'Name',
email: 'Email',
password: 'Password',
enterName: 'Enter your name',
enterEmail: 'Enter your email',
enterPassword: 'Enter your password',
forgetPassword: 'Forget password?',
pleaseInputName: 'Please input the name',
nameLengthError: 'Name length must be between {min} and {max} characters',
pleaseInputEmail: 'Please input the email',
emailFormatError: 'Please input the email again',
pleaseInputPassword: 'Please input the password',
passwordLengthError: 'Password length must be between {min} and {max} characters',
pleaseTermsPolicy: 'Please agree to the Terms, Policy and Fees',
agreeTermsPolicy:
'I agree to the <span onclick="onClickPrivacy()">Terms, Policy</span> and Fees.',
noAccountToSignUp: `Don't have an account? <span onclick="onClickRegister()">Sign up</span>`,
registerFor: 'Register for',
registerTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
havenAccountToLogin: `Already have an account? <span onclick="onClickLogin()">Log in</span>`,
verifyEmail: 'Verify your email address',
verifyCodeHasSent: 'A verification code has been sent to <span>{email}</span>',
verify: 'Verify',
resendCode: 'Resend Code',
resendCodeIn: 'Resend Code in {time}',
orContinueWith: 'or continue with',
googleLogin: 'Sign in with Google',
wechatLogin: 'Sign in with Wechat'
},
Nuic: {
hiName: 'Hi, {name}.',
nuic1Title: `Help Fiphant 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.`,
letsGo: 'Lets go, Fiphant!',
skip: 'Skip',
next: 'Next',
nuic2Title: `What's your dream <b>home vibe</b> ?`,
loadMore: 'Load more',
nuic3Title: `<b>Where</b> are you based? What do you <b>do</b> ?`,
basedIn: 'Based in',
role: 'Role',
allSet: 'All set!'
},
Home: {
creditsNum: 'Credits: {num}',
newProject: 'New Project',
home: 'Home',
history: 'History',
today: 'Today',
yesterday: 'Yesterday',
earlierChat: 'Earlier Chat'
},
Input: {
placeholder: 'Please input',
selectPlaceholder: 'Please select',
@@ -17,10 +74,10 @@ export default {
Traditional: 'Traditional',
CenturyChrome: 'Century\nChrome',
ModernRevival: 'Modern\nRevival',
Tuscan2000s: "Tuscan\n2000's",
Bauhaus: 'Bauhaus',
Constructivism: 'Constructivism',
NordicNoir:'Nordic\nNoir',
Tuscan2000s: "Tuscan\n2000's",
Bauhaus: 'Bauhaus',
Constructivism: 'Constructivism',
NordicNoir: 'Nordic\nNoir'
},
chooseStyle: 'Choose Style',
setting: 'Setting',
@@ -30,9 +87,9 @@ export default {
relevance: 'Relevance'
},
confirm: 'Confirm',
styleTitle:'Settings',
styleTitle: 'Settings',
createProject: 'Create Project',
trendingReport: 'Trending report'
trendingReport: 'Trending Report'
},
area: {
unitedStates: 'United States',
@@ -45,5 +102,14 @@ export default {
japan: 'Japan',
canada: 'Canada',
germany: 'Germany'
},
// Version Tree
VersionTree: {
versionInformation: 'Version Information',
input: 'Input',
userRequest: 'User Request',
sketch: 'Sketch',
generateResult: 'Generate Result'
}
}

View File

@@ -4,9 +4,11 @@ import { createI18n } from 'vue-i18n'
// element-plus 中的语言配置
import elementEnLocale from './en'
import elementZhLocale from './zh-cn'
// 自己的语言配置
import enLocale from './en'
import zhLocale from './zh-cn'
// 语言配置整合
const messages = {
@@ -14,14 +16,15 @@ const messages = {
...enLocale,
...elementEnLocale
},
// 'CHINESE_SIMPLIFIED':{
// ...zhLocale,
// ...elementZhLocale
// },
'CHINESE_SIMPLIFIED':{
...zhLocale,
...elementZhLocale
},
}
// 创建 i18n
const i18n = createI18n({
warnHtmlMessage: false,
legacy: false,
globalInjection:true, // 全局模式,可以直接使用 $t
locale: 'ENGLISH',

View File

@@ -1,5 +1,62 @@
export default {
Login: {},
Login: {
login: '登录',
register: '注册',
signUp: '注册',
loginTo: '登录到',
loginTitle: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
name: '姓名',
email: '邮箱',
password: '密码',
enterName: '请输入姓名',
enterEmail: '请输入邮箱',
enterPassword: '请输入密码',
forgetPassword: '忘记密码?',
pleaseInputName: '请输入姓名',
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
pleaseInputEmail: '请输入邮箱',
emailFormatError: '请输入正确的邮箱',
pleaseInputPassword: '请输入密码',
passwordLengthError: '密码长度必须在 {min} 到 {max} 个字符之间',
pleaseTermsPolicy: '请同意条款、政策和费用',
agreeTermsPolicy: '我同意 <span onclick="onClickPrivacy()">条款、政策</span> 和费用。',
noAccountToSignUp: `还没有账号? <span onclick="onClickRegister()">注册</span>`,
registerFor: '注册账号',
registerTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
havenAccountToLogin: `已经有账号? <span onclick="onClickLogin()">登录</span>`,
verifyEmail: '验证您的邮箱地址',
verifyCodeHasSent: '已发送验证码到 <span>{email}</span>',
verifyCode: '请输入验证码',
verify: '验证',
resendCode: '重新发送验证码',
resendCodeIn: '重新发送验证码倒计时 {time}',
orContinueWith: '或者使用',
googleLogin: '使用 Google 登录',
wechatLogin: '使用微信登录'
},
Nuic: {
hiName: '你好,{name}。',
nuic1Title: `帮助 Fiphant 发现您空间中的 <b>'YOU'</b>。`,
nuic1Tip: `让我们设置您的个人资料。几个快速的细节将帮助 Fiphant 理解您的需求并找到您正在寻找的内容。`,
letsGo: '让我们开始Fiphant',
skip: '跳过',
next: '下一步',
nuic2Title: `您理想中 <b>家的氛围</b> 是什么?`,
loadMore: '加载更多',
nuic3Title: `您在 <b>哪里</b> 工作?您从事什么 <b>工作</b> `,
basedIn: '公司',
role: '角色',
allSet: '准备好了!'
},
Home: {
creditsNum: '积分: {num}',
newProject: '新建项目',
home: '首页',
history: '历史记录',
today: '今天',
yesterday: '昨天',
earlierChat: '更早的'
},
Input: {
placeholder: '请输入',
selectPlaceholder: '请选择',

View File

@@ -29,13 +29,18 @@ const router = createRouter({
name: 'register',
component: () => import('../views/login/register.vue'),
},
{
path: '/nuic',
name: 'nuic',
component: () => import('../views/nuic/index.vue'),
},
{
path: '/home',
name: 'home',
component: () => import('../views/home/index.vue'),
children: [
{
path: 'test',
path: 'test/:id',
name: 'test',
component: () => import('../views/home/test.vue'),
meta: { topNavStyle: '2' }

View File

@@ -30,9 +30,11 @@
flex-direction: column;
> .bottom-view {
flex: 1;
// background-color: #fff;
overflow: hidden;
display: flex;
> * {
flex: 1;
}
}
}
}

View File

@@ -9,15 +9,15 @@
</div>
<button class="create-btn">
<span class="icon"><svg-icon name="add" size="16" /></span>
<span v-show="!isCollapse" class="text">New Project</span>
<span v-show="!isCollapse" class="text">{{ $t('Home.newProject') }}</span>
</button>
<!-- <div class="menu-item" @click="onHome">
<span class="icon"><svg-icon name="home" size="24" /></span>
<span class="title" v-show="!isCollapse">Home</span>
<span class="title" v-show="!isCollapse">{{ $t('Home.home') }}</span>
</div> -->
<div class="menu-item" @click="onHistory" :class="{ active: showHistory }">
<span class="icon"><svg-icon name="history" size="24" /></span>
<span class="title" v-show="!isCollapse">History</span>
<span class="title" v-show="!isCollapse">{{ $t('Home.history') }}</span>
<span class="icon jiantou" v-show="!isCollapse"
><svg-icon name="arrow-right" size="14" />
</span>
@@ -25,15 +25,24 @@
<div class="history-list" v-show="!isCollapse && showHistory">
<div v-for="item in historyList" :key="item.name" class="history-item">
<div v-if="item.title" class="title">{{ item.name }}</div>
<div v-else class="box">
<div
v-else
class="box"
@click="onClickHistoryItem(item)"
:class="{ active: item.id == id }"
>
<span>{{ item.name }}</span>
<el-popover placement="right" trigger="click">
<el-popover
placement="right"
trigger="click"
popper-style="padding: 1rem 0.5rem;"
>
<template #reference>
<span class="icon"><svg-icon name="more" size="16" /></span>
<span @click.stop class="icon"><svg-icon name="more" size="16" /></span>
</template>
<div class="button-box">
<div class="rename-btn">Rename</div>
<div class="delete-btn">Delete</div>
<div class="history-item-menu">
<div class="rename" @click="onRenameHistoryItem(item)">Rename</div>
<div class="delete" @click="onDeleteHistoryItem(item)">Delete</div>
</div>
</el-popover>
</div>
@@ -45,9 +54,12 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useI18n } from 'vue-i18n'
const { t: $t } = useI18n()
const route = useRoute()
const router = useRouter()
import { useGlobalStore } from '@/stores'
const id = computed(() => route.params.id)
const globalStore = useGlobalStore()
const isCollapse = computed(() => globalStore.state.homeLeftNavCollapse)
const onCollapse = () => {
@@ -57,29 +69,34 @@
const historyList = ref([
{
title: true,
name: 'Today'
name: $t('Home.today')
},
{
id: 1,
name: 'Conversation Item 1'
},
{
id: 2,
name: 'Conversation Item 2'
},
{
title: true,
name: 'Yesterday'
name: $t('Home.yesterday')
},
{
id: 3,
name: 'Conversation Item 3'
},
{
title: true,
name: 'Earlier Chat'
name: $t('Home.earlierChat')
},
{
id: 4,
name: 'Conversation Item 4'
},
{
id: 5,
name: 'Conversation Item 5'
}
])
@@ -89,6 +106,22 @@
const onHistory = () => {
showHistory.value = !showHistory.value
}
const onClickHistoryItem = (item: any) => {
router.push({ name: 'test', params: { id: item.id } })
}
const onRenameHistoryItem = (item: any) => {
// const index = historyList.value.findIndex((i: any) => i.id == item.id)
// if (index != -1) {
// }
}
const onDeleteHistoryItem = (item: any) => {
console.log(item)
const index = historyList.value.findIndex((i: any) => i.id == item.id)
if (index != -1) {
historyList.value.splice(index, 1)
}
}
</script>
<style lang="less" scoped>
@@ -120,7 +153,7 @@
margin-right: 1rem;
}
> .logo-text {
font-family: Mazzard;
font-family: SemiBold;
font-weight: 600;
font-size: 3rem;
margin-right: auto;
@@ -193,14 +226,19 @@
> .title {
font-weight: 600;
font-size: 1.6rem;
font-family: SemiBold;
}
> .box {
font-family: Regular;
border-radius: 0.8rem;
cursor: pointer;
&.active,
&:hover {
background-color: rgba(0, 0, 0, 0.06);
}
&.active {
font-family: SemiBold;
}
> .label {
flex: 1;
font-weight: 400;
@@ -209,8 +247,28 @@
white-space: nowrap;
overflow: hidden;
}
>.icon{
width: 2.5rem;
height: 2.5rem;
}
}
}
}
}
.history-item-menu {
user-select: none;
> div {
cursor: pointer;
padding: 0.5rem 1rem;
&:hover {
background-color: rgba(0, 0, 0, 0.06);
}
}
> .rename {
color: #409eff;
}
> .delete {
color: #ff4d4f;
}
}
</style>

View File

@@ -1,19 +1,21 @@
<template>
<div class="test">
<p>老八秘制小汉堡</p>
<p>Conversation Item - {{ id }}</p>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
const id = computed(() => route.params.id)
</script>
<style lang="less" scoped>
.test {
flex: 1;
margin: 10px;
border-radius: 10px;
// background-color: rgb(242, 130, 90);
margin: 2rem;
border-radius: 2rem;
background-color: rgb(242, 130, 90);
display: flex;
align-items: center;
justify-content: center;

View File

@@ -5,7 +5,7 @@
</span>
<p class="division"></p>
<div class="credits-box">
<span class="credits">Credits: 6000</span>
<span class="credits">{{ $t('Home.creditsNum', { num: 6000 }) }}</span>
<span class="icon" @click="onRefresh" :class="{ loading }">
<svg-icon name="refresh" size="21" />
</span>

View File

@@ -0,0 +1,103 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import userHead from '@/assets/images/chatVersion/userHead.png'
import robotHead from '@/assets/images/chatVersion/robotHead.png'
import { useI18n } from 'vue-i18n'
const { t: $t } = useI18n()
const props = defineProps({
type: {
type: String,
default: 'user'
},
})
//const emit = defineEmits([
//])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="ChatHistory">
<div class="titleInfo">
<div class="userInfo">
<img :src="type == 'user'? userHead:robotHead" alt="">
<span>{{ type == 'user'? $t('VersionTree.input'): $t('VersionTree.sketch') }}</span>
</div>
<div class="time">
<span>12:00:00</span>
</div>
</div>
<div class="infoTitle">{{ type == 'user'? $t('VersionTree.userRequest'): $t('VersionTree.generateResult') }}</div>
<div class="history">
Design a modern yellow sofa that combines comfort, elegance, and contemporary aesthetics. The sofa features a warm, soft yellow tone (mustard or light ochre), with a minimalist silhouette and clean lines. Upholstered in high-quality fabric with a subtle texture, offering a cozy and inviting feel. The seat is deep and plush, with generous cushioning for relaxation, while the backrest and armrests are softly rounded to enhance comfort.
Design a modern yellow sofa that combines comfort, elegance, and contemporary aesthetics. The sofa features a warm, soft yellow tone (mustard or light ochre), with a minimalist silhouette and clean lines. Upholstered in high-quality fabric with a subtle texture, offering a cozy and inviting feel. The seat is deep and plush, with generous cushioning for relaxation, while the backrest and armrests are softly rounded to enhance comfort.
</div>
</div>
</template>
<style lang="less" scoped>
.ChatHistory{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
> .titleInfo{
height: 2.4rem;
display: flex;
justify-content: space-between;
> .userInfo{
display: flex;
align-items: center;
> img{
margin-right: .2rem;
}
> span{
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.4rem;
line-height: 2rem;
color: #000;
opacity: .5;
}
}
> .time{
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.4rem;
line-height: 2rem;
opacity: .5;
}
}
> .infoTitle{
margin-top: .6rem;
font-size: 1.2rem;
line-height: 2rem;
margin-left: 3rem;
}
> .history{
margin-top: .8rem;
font-family: 'Regular';
font-weight: 400;
font-size: 1.2rem;
line-height: 1.4rem;
letter-spacing: Letter Spacing;
flex: 1;
overflow-y: auto;
margin-left: 3rem;
scrollbar-width: thin;
scrollbar-color: #ababab #f1f5f9;
-ms-overflow-style: -ms-autohiding-scrollbar;
&::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 10px;
}
}
}
</style>

View File

@@ -0,0 +1,71 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import VersionDetail from './versionDetail.vue'
import ChatHistory from './chatHistory.vue'
//const props = defineProps({
//})
//const emit = defineEmits([
//])
const detailData = ref({
id:1,
versionDetail:{
version:'1.0.0',
versionTime:'2023-08-01 10:00:00',
versionSketch:'Version 1 - Sketch',
versionSketchTime:'2023-08-01 10:00:00',
},
userChatHistory:{
}
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
// const {} = toRefs(data);
</script>
<template>
<div class="detailBox">
<div class="versionDetail">
<VersionDetail :versionDetail="detailData.versionDetail"></VersionDetail>
</div>
<div class="useInput">
<ChatHistory type="user"></ChatHistory>
</div>
<div class="systemInput">
<ChatHistory type="robot"></ChatHistory>
</div>
</div>
</template>
<style lang="less" scoped>
.detailBox{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
> div{
border-radius: var(--border-radius, 1rem);
border: 1px solid #D9D9D9;
background-color: #f7f7f7;
margin-bottom: 3.6rem;
padding: 1.5rem 1.4rem;
width: 100%;
&.versionDetail{
height: 21rem;
}
&.useInput{
height: 21.5rem;
}
&.systemInput{
flex: 1;
}
&:last-child{
margin-bottom: 0;
}
}
}
</style>

View File

@@ -0,0 +1,77 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { useI18n } from 'vue-i18n'
const { t: $t } = useI18n()
const props = defineProps({
versionDetail: {
type: Object,
default: () => ({})
}
})
//const emit = defineEmits([
//])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="versionDetail">
<div class="title">
<span class="titleText">
{{ $t('VersionTree.versionInformation') }}
</span>
<span class="icon"><svg-icon name="more" size="25" /></span>
</div>
<div class="version">{{versionDetail.version}}</div>
<div class="time marBott1">{{versionDetail.versionTime}}</div>
<div class="version gray">{{versionDetail.versionSketch}}</div>
<div class="time gray">{{versionDetail.versionSketchTime}}</div>
</div>
</template>
<style lang="less" scoped>
.versionDetail{
width: 100%;
height: 100%;
position: relative;
> .title{
line-height: 2rem;
font-size: 1.4rem;
color: #000;
display: flex;
justify-content: space-between;
height: 2rem;
margin-bottom: 2.8rem;
> .titleText{
opacity: .5;
font-weight: 600;
font-family: 'SemiBold';
}
> .icon{
color: #5a5a5a;
}
}
> .version{
font-family: 'SemiBold';
font-weight: 600;
font-size: 1.4rem;
margin-bottom: .2rem;
line-height: 2rem;
}
> .marBott1{
margin-bottom: 1.2rem;
}
> .time{
font-weight: 500;
font-size: 1.2rem;
line-height: 2rem;
}
> .gray{
color: #C1C1C1;
}
}
</style>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import Tree from './tree/index.vue'
import Detail from './detail/index.vue'
const props = defineProps({
versionTreeData:{
type:Object,
@@ -16,6 +17,8 @@ const props = defineProps({
//])
const treeState = ref(true)//
const selectItem = ref({})
const openTree = ()=>{
treeState.value = !treeState.value
@@ -47,16 +50,16 @@ const {} = toRefs(data);
</div>
</div>
</div>
<div style="display: flex;">
<div style="display: flex;" class="expandBtnBox">
<el-button class="expandBtn" @click="openTree" style="width: 5rem;">+</el-button>
<el-button class="expandBtn" @click="openTree" style="width: 5rem;">-</el-button>
</div>
<div class="versionTreeBox">
<div class="tree">
<Tree :treeState="treeState"></Tree>
<Tree :treeState="treeState" v-model:selectItem="selectItem"></Tree>
</div>
<div class="detail">
<Detail v-model:selectItem="selectItem"></Detail>
</div>
</div>
</el-drawer>
@@ -83,17 +86,21 @@ const {} = toRefs(data);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 .8rem 0 1.2rem;
padding: 0 .8rem 0 2.4rem;
border-bottom: 1px solid #C9C9C9;
> span{
font-size: 2rem;
font-weight: 600;
font-family: 'SemiBold';
}
> .closeBtn{
width: 2.4rem;
height: 2.4rem;
cursor: pointer;
}
}
.expandBtnBox{
}
.versionTreeBox{
flex: 1;
@@ -103,14 +110,13 @@ const {} = toRefs(data);
flex: 1;
height: 100%;
overflow: hidden;
padding: 1.8rem 0 1.8rem 2.1rem;
padding: 2.1rem 0 5.4rem 2.2rem;
}
> .detail{
width: 35rem;
margin: 1.4rem 3rem 0 3.4rem;
height: 100%;
overflow: auto;
background-color: #F5F5F5;
margin: 2.1rem 3rem 5.4rem 3.4rem;
height: calc(100% - 2.1rem - 5.4rem);
overflow: hidden;
}
}
}

View File

@@ -8,12 +8,18 @@ const props = defineProps({
treeState:{
default:false,
},
selectItem: {
type: Object,
default: () => ({})
} as any,
})
//const emit = defineEmits([
//])
const emit = defineEmits([
'update:selectItem'
])
let data = reactive({
})
const isLoad = ref(false)
const treeStateTime = ref(true)
watch(()=>props.treeState,(newVal,oldVal)=>{
@@ -28,7 +34,7 @@ const pushView2Item = (item)=>{
view2Ref.value.push(item)
}
const view1List = ref([
const treeList = ref([
{
name:'P1',
},{
@@ -37,9 +43,34 @@ const view1List = ref([
name:'V1-1',
}
])
function traverseArray(items, callback) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
callback(item, i)
if (item.child && Array.isArray(item.child) && item.child.length > 0) {
traverseArray(item.child, callback)
}
}
}
const initialize = ()=>{
isLoad.value = false
setSelectItem(versionsList[0])
treeList.value = []
traverseArray(versionsList, (item, index) => {
treeList.value.push(item)
})
isLoad.value = true
}
const setSelectItem = (item)=>{
console.log(item[0])
emit('update:selectItem', {...item})
}
onMounted(()=>{
// addView2Item()
view2Ref.value.init(versionsList)
initialize()
})
onUnmounted(()=>{
})
@@ -47,12 +78,17 @@ defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="tree" v-show="treeStateTime">
<div class="tree" v-show="treeStateTime" v-if="isLoad">
<div v-show="!treeState" class="box view1">
<view1Item v-for="item in view1List" :key="item.name" :item="item"></view1Item>
<view1Item v-for="item in treeList" :key="item.name" :item="item" @click="emit('selectItem', item)"></view1Item>
</div>
<div v-show="treeState" class="box view2">
<view2 ref="view2Ref"></view2>
<view2
ref="view2Ref"
@setSelectItem="setSelectItem"
:treeList="treeList"
:selectItem="props.selectItem"
></view2>
</div>
</div>
</template>
@@ -67,7 +103,9 @@ const {} = toRefs(data);
box-sizing: border-box;
&.view1{
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
}
&.view2{
border: 1px solid #D9D9D9;

View File

@@ -13,11 +13,9 @@ const props = defineProps<{
</script>
<template>
<div class="node">
<Handle type="target" id="top" :position="Position.Top" />
<Handle type="source" id="bottom" :position="Position.Bottom" />
<Handle type="source" id="right" :position="Position.Right" />
<div>{{ props.data.id }}</div>
<div class="node start">
<Handle type="source" id="Bottom" :position="Position.Bottom" />
<div>index</div>
</div>
</template>

View File

@@ -1,39 +1,40 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, nextTick } from "vue";
import { ref, onMounted, onUnmounted, reactive, nextTick, watch } from "vue";
import type { Node, Edge } from '@vue-flow/core'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import SpecialEdge from './speciaiEdge.vue'
import PrimaryNode from './primaryNode.vue'//主
import InputNode from './InputNode.vue'//主
import SecondaryNode from './secondaryNode.vue'//分支
import { useLayout } from './tools/tools'
const props = defineProps({
item: {
selectItem: {
type: Object,
default: () => ({})
} as any,
treeList: {
type: Array,
default: () => []
}
})
//const emit = defineEmits([
//])
let selectId = ref(2)
const isLoad = ref(false)
const emit = defineEmits([
'setSelectItem',
])
// 节点类型input、output、default、custom
// input:开始点output结尾点default普通节点custom自定义节点
const position = { x: 0, y: 0 }
const nodes = ref<Node[]>([
// { id: '1', type: 'input', label: 'Node 1', class: 'custom-node start', position, sourcePosition: 'bottom' },
// { id: '2', type: 'PrimaryNode', class: 'custom-node', data: { id: '主 1' }, position },
// { id: '2-1', type: 'SecondaryNode', class: 'custom-node', data: { id: '分 1-1' }, position },
// { id: '3', type: 'PrimaryNode', class: 'custom-node', data: { id: '主 2' }, position },
// { id: '1', type: 'input', label: 'Node 1', class: 'custom-node start', position },
// { id: '2', type: 'SecondaryNode', class: 'custom-node', data: { id: '主 1' }, position },
// { id: '2-1', type: 'SecondaryNode', class: 'custom-node', data: { id: '主 2' }, position },
// { id: '3', type: 'SecondaryNode', class: 'custom-node', data: { id: '主 3' }, position },
])
// 边类型custom、default
// custom自定义边default普通边step直角边smoothstep平滑边
const edges = ref<Edge[]>([
// { id: 'e1-2', source: '1', target: '2', type: 'smoothstep' },
// { id: 'e1-3', source: '2', target: '2-1', type: 'smoothstep', sourceHandle:'right',},
// { id: 'e1-4', source: '2', target: '3', type: 'smoothstep',sourceHandle:'bottom', animated: true },
// { id: 'e1-3', source: '2', target: '2-1', type: 'smoothstep', },
// { id: 'e1-4', source: '2', target: '3', type: 'smoothstep', animated: true },
])
const { fitView } = useVueFlow()
const { layout } = useLayout()
@@ -47,37 +48,19 @@ async function layoutGraph(direction) {
}, 0)
}
let elIndex = 1
const push = (item)=>{
if(nodes.value.length == 0){
nodes.value.push({ id: '0', type: 'input', label: 'Node 1', class: 'custom-node start', position, sourcePosition: 'bottom' })
nodes.value.push({ id: '0', type: 'InputNode', class: 'custom-node', position })
}
let className = 'custom-node'
let className = `custom-node item${item.id.replace(/-/g, "_")}`
let id = item.id
let target = edges.value.length == 0?'0':item.id.slice(0, -2)
let source = edges.value.length == 0?'0':item.id.slice(0, -2)
nodes.value.push({id,type:'SecondaryNode',class:className,position,data:item})
edges.value.push({ id, source: id, target, type: 'smoothstep' })
console.log()
edges.value.push({ id, target:id, source, type: 'smoothstep' })
}
function traverseArray(items, callback) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
callback(item, i)
if (item.child && Array.isArray(item.child) && item.child.length > 0) {
traverseArray(item.child, callback)
}
}
}
const init = (list)=>{
isLoad.value = false
traverseArray(list, (item, index) => {
console.log()
push(item)
})
isLoad.value = true
console.log(nodes.value,edges.value)
const initialized = ()=>{
layoutGraph('TB')
}
//是否可拖动节点
@@ -86,29 +69,39 @@ const toggleNodesDraggable = () => {
nodesDraggable.value = !nodesDraggable.value
}
const handleVueFlowNodeClick = (node: Node) => {
console.log(node)
const handleVueFlowNodeClick = ({node}) => {
if(node.data.id)emit('setSelectItem', node.data)
}
watch(()=>props.treeList.length, (newVal, oldVal) => {
nodes.value = []
edges.value = []
props.treeList.forEach(item=>{
push(item)
})
},{immediate:true})
watch(()=>props.selectItem.id, (newVal, oldVal) => {
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({init,push})
defineExpose({push})
// const {} = toRefs(data);
</script>
<template>
<div class="view2">
<div class="vueFlowBox" v-if="isLoad">
<div class="vueFlowBox">
<div @click="toggleNodesDraggable">拖拽节点</div>
<VueFlow :nodes="nodes" @nodes-initialized="layoutGraph('LR')" :edges="edges" @node-click="handleVueFlowNodeClick" :nodes-draggable="nodesDraggable">
<template #node-PrimaryNode="nodeProps">
<PrimaryNode v-bind="nodeProps" />
<VueFlow :nodes="nodes" @nodes-initialized="initialized" :edges="edges" @node-click="handleVueFlowNodeClick" :nodes-draggable="nodesDraggable">
<template #node-InputNode="nodeProps">
<InputNode v-bind="nodeProps" />
</template>
<template #node-SecondaryNode="nodeProps">
<SecondaryNode
v-bind="nodeProps"
:selectId="selectId"
:selectItem="props.selectItem"
/>
</template>
@@ -135,41 +128,31 @@ defineExpose({init,push})
overflow: hidden;
}
:deep(.custom-node){
--vf-handle: #000;
--vf-node-color: #000;
--vf-box-shadow: #000;
font-size: 1.2rem;
width: var(--treeItem-width);
height: var(--treeItem-height);
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--treeItem-raduis);
border: var(--treeItem-border);
color: #000;
cursor: pointer;
background-color: var(--treeItem-background);
box-sizing: border-box;
.vue-flow__handle-right{
transform: translate(calc(50% + 2px), -50%);
}
.vue-flow__handle-left{
transform: translate(calc(-50% - 2px), -50%);
}
.vue-flow__handle-top{
transform: translate(-50%, calc(-50% - 2px));
}
.vue-flow__handle-bottom{
transform: translate(-50%, calc(50% + 2px));
}
&.active{
background-color: var(--treeItem-active-background);
}
&.start{
background-color: #7A7A7A;
color: #FFF;
border: 2px solid #7A7A7A;
.node{
--vf-handle: #000;
--vf-node-color: #000;
--vf-box-shadow: #000;
font-size: 1.2rem;
width: var(--treeItem-width);
height: var(--treeItem-height);
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--treeItem-raduis);
border: var(--treeItem-border);
color: #000;
cursor: pointer;
background-color: var(--treeItem-background);
box-sizing: border-box;
&.active{
background-color: var(--treeItem-active-background);
}
&.start{
background-color: #7A7A7A;
color: #FFF;
border: 2px solid #7A7A7A;
}
}
}
}

View File

@@ -7,15 +7,21 @@ const props = defineProps<{
default: () => ({
id: '',
})
}
},
selectItem: {
type: Object,
default: () => {},
},
}>()
</script>
<!-- source输入target输出 -->
<template>
<div class="node">
<Handle type="target" id="left" :position="Position.Left" />
<Handle type="source" id="right" :position="Position.Right" />
<div class="node" :class="{active:props.selectItem.id == props.data.id}">
<Handle type="target" id="Top" :position="Position.Top" />
<Handle type="source" id="Bottom" :position="Position.Bottom" />
<!-- <Handle type="target" id="Bottom" :position="Position.Bottom" />
<Handle type="source" id="Top" :position="Position.Top" /> -->
<div>{{ props.data.id }}</div>
</div>
</template>

View File

@@ -8,50 +8,79 @@ import { ref } from 'vue'
* It uses the `dagre` library to calculate the layout of the nodes and edges.
*/
export function useLayout() {
const { findNode } = useVueFlow()
const { findNode } = useVueFlow()
const graph = ref(new dagre.graphlib.Graph())
const graph = ref(new dagre.graphlib.Graph())
const previousDirection = ref('LR')
const previousDirection = ref('LR')
function layout(nodes, edges, direction) {
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
const dagreGraph = new dagre.graphlib.Graph()
function layout(nodes, edges, direction = 'LR') {
// 验证和规范化方向参数
const validDirections = ['TB', 'BT', 'LR', 'RL']
const layoutDirection = validDirections.includes(direction) ? direction : 'LR'
graph.value = dagreGraph
// we create a new graph instance, in case some nodes/edges were removed, otherwise dagre would act as if they were still there
const dagreGraph = new dagre.graphlib.Graph()
dagreGraph.setDefaultEdgeLabel(() => ({}))
graph.value = dagreGraph
const isHorizontal = direction === 'LR'
dagreGraph.setGraph({ rankdir: direction })
dagreGraph.setDefaultEdgeLabel(() => ({}))
previousDirection.value = direction
// 根据方向判断是否为水平布局
const isHorizontal = layoutDirection === 'LR' || layoutDirection === 'RL'
dagreGraph.setGraph({ rankdir: layoutDirection })
for (const node of nodes) {
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
const graphNode = findNode(node.id)
previousDirection.value = layoutDirection
dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
}
for (const node of nodes) {
// if you need width+height of nodes for your layout, you can use the dimensions property of the internal node (`GraphNode` type)
const graphNode = findNode(node.id)
for (const edge of edges) {
dagreGraph.setEdge(edge.source, edge.target)
}
dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
}
dagre.layout(dagreGraph)
for (const edge of edges) {
dagreGraph.setEdge(edge.source, edge.target)
}
// set nodes with updated positions
return nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
dagre.layout(dagreGraph)
return {
...node,
targetPosition: isHorizontal ? Position.Left : Position.Top,
sourcePosition: isHorizontal ? Position.Right : Position.Bottom,
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
}
})
}
// set nodes with updated positions
return nodes.map((node) => {
const nodeWithPosition = dagreGraph.node(node.id)
return { graph, layout, previousDirection }
}
// 根据方向动态计算连接点位置
let targetPosition, sourcePosition
switch (layoutDirection) {
case 'BT': // 从上到下 (Top to Bottom)
targetPosition = Position.Bottom // 目标节点连接点在下方
sourcePosition = Position.Top // 源节点连接点在上方
break
case 'TB': // 从下到上 (Bottom to Top)
targetPosition = Position.Top // 目标节点连接点在上方
sourcePosition = Position.Bottom // 源节点连接点在下方
break
case 'LR': // 从左到右 (Left to Right)
targetPosition = Position.Left
sourcePosition = Position.Right
break
case 'RL': // 从右到左 (Right to Left)
targetPosition = Position.Right
sourcePosition = Position.Left
break
default:
targetPosition = Position.Top
sourcePosition = Position.Bottom
}
return {
...node,
targetPosition,
sourcePosition,
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
}
})
}
return { graph, layout, previousDirection }
}

View File

@@ -4,27 +4,29 @@ export const versionsList = [
name:'V1',
child:[
{
id: '1-1',
name:'V1-1',
child:[
{
id: '1-1-1',
name:'V1-1-1',
}
]
},{
id: '1-1',
name:'V1-1',
child:[
{
id: '1-1-1',
name:'V1-1-1',
}
]
},
{
id: '1-2',
name:'V1-2',
child:[
{
id: '1-2-1',
name:'V1-2-1',
},{
id: '1-2-2',
name:'V1-2-2',
}
{
id: '1-2-1',
name:'V1-2-1',
},{
id: '1-2-2',
name:'V1-2-2',
}
]
},{
},
{
id: '1-2',
name:'V1-2',
child:[
@@ -51,7 +53,8 @@ export const versionsList = [
]
},
]
},{
},
{
id: '1-3',
name:'V1-3',
}

View File

@@ -5,6 +5,7 @@
overflow: hidden;
padding: 2.5rem;
display: flex;
user-select: none;
}
.register > .left,
.login > .left {
@@ -35,6 +36,7 @@
.login > .left > .logo > span {
font-weight: 600;
font-size: 3.3rem;
font-family: SemiBold;
}
.register > .right,
.login > .right {
@@ -96,8 +98,7 @@
.register > .right > .box > .tip,
.login > .right > .box > .tip {
font-weight: 400;
font-family: General Sans Variable;
font-style: Regular;
font-family: Regular;
font-size: 1.8rem;
color: #666;
margin-top: 0.4rem;
@@ -107,6 +108,10 @@
margin-top: 5rem;
width: 100%;
}
.register > .right > .box > .el-form::v-deep,
.login > .right > .box > .el-form::v-deep {
font-family: Regular;
}
.register > .right > .box > .el-form::v-deep .el-form-item,
.login > .right > .box > .el-form::v-deep .el-form-item {
margin-bottom: 2rem;
@@ -116,6 +121,7 @@
color: #252727;
font-size: 1.8rem;
margin-bottom: 0.8rem;
font-family: Medium;
}
.register > .right > .box > .el-form::v-deep .el-input,
.login > .right > .box > .el-form::v-deep .el-input {
@@ -146,8 +152,8 @@
color: #666666;
font-weight: 400;
}
.register > .right > .box > .el-form::v-deep .privacy .el-checkbox__label > span,
.login > .right > .box > .el-form::v-deep .privacy .el-checkbox__label > span {
.register > .right > .box > .el-form::v-deep .privacy .el-checkbox__label > div > span,
.login > .right > .box > .el-form::v-deep .privacy .el-checkbox__label > div > span {
text-decoration: underline;
cursor: pointer;
}
@@ -165,15 +171,17 @@
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
font-family: SemiBold;
}
.register > .right > .box > .tip-2,
.login > .right > .box > .tip-2 {
font-weight: 400;
font-size: 1.6rem;
color: #666;
font-family: Regular;
}
.register > .right > .box > .tip-2 > span,
.login > .right > .box > .tip-2 > span {
.register > .right > .box > .tip-2::v-deep > span,
.login > .right > .box > .tip-2::v-deep > span {
text-decoration: underline;
color: #FF7A50;
cursor: pointer;

View File

@@ -2,8 +2,8 @@
<div class="index background-pink">
<div class="header">
<p class="split"></p>
<button class="login" @click="onLogin">Log in</button>
<button class="register" @click="onRegister">Sign up</button>
<button class="login" @click="onLogin">{{ $t('Login.login') }}</button>
<button class="register" @click="onRegister">{{ $t('Login.signUp') }}</button>
</div>
</div>
</template>
@@ -46,6 +46,7 @@
outline: none;
font-size: 2.2rem;
font-weight: 600;
font-family: SemiBold;
&:active {
opacity: 0.8;
}

View File

@@ -5,6 +5,7 @@
overflow: hidden;
padding: 2.5rem;
display: flex;
user-select: none;
>.left {
flex: 1;
@@ -32,6 +33,7 @@
>span {
font-weight: 600;
font-size: 3.3rem;
font-family: SemiBold;
}
}
}
@@ -93,8 +95,8 @@
>.tip {
font-weight: 400;
font-family: General Sans Variable;
font-style: Regular;
font-family: Regular;
// font-style: Regular;
font-size: 1.8rem;
color: #666;
margin-top: 0.4rem;
@@ -105,6 +107,8 @@
width: 100%;
&::v-deep {
font-family: Regular;
.el-form-item {
margin-bottom: 2rem;
}
@@ -113,6 +117,7 @@
color: #252727;
font-size: 1.8rem;
margin-bottom: 0.8rem;
font-family: Medium;
}
.el-input {
@@ -143,9 +148,11 @@
color: #666666;
font-weight: 400;
>span {
text-decoration: underline;
cursor: pointer;
>div {
>span {
text-decoration: underline;
cursor: pointer;
}
}
}
}
@@ -163,6 +170,7 @@
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
font-family: SemiBold;
}
}
}
@@ -171,8 +179,9 @@
font-weight: 400;
font-size: 1.6rem;
color: #666;
font-family: Regular;
>span {
&::v-deep>span {
text-decoration: underline;
color: #FF7A50;
cursor: pointer;

View File

@@ -17,38 +17,42 @@
<img src="@/assets/images/login/elephant.png" />
<template v-if="!isVisible">
<div class="title">
<span>Log on to</span>
<span>{{ $t('Login.loginTo') }}</span>
<img src="@/assets/images/logo-2.png" />
</div>
<div class="tip">A multi-agent canvas for rapid, trend driven design iteration.</div>
<div class="tip">{{ $t('Login.loginTitle') }}</div>
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
<el-form-item label="Email" prop="email">
<el-input v-model="formData.email" placeholder="Enter your email" name="email" />
<el-form-item :label="$t('Login.email')" prop="email">
<el-input
v-model="formData.email"
:placeholder="$t('Login.enterEmail')"
name="email"
/>
</el-form-item>
<el-form-item label="Password" prop="password">
<el-form-item :label="$t('Login.password')" prop="password">
<el-input
v-model="formData.password"
placeholder="Enter your password"
:placeholder="$t('Login.enterPassword')"
type="password"
show-password
name="password"
/>
</el-form-item>
<div class="forgetPassword">
<span>forget password?</span>
<span>{{ $t('Login.forgetPassword') }}</span>
</div>
<el-form-item prop="privacy" class="privacy">
<el-checkbox v-model="formData.privacy">
I agree to the <span @click.prevent="onClickPrivacy">Terms, Policy</span> and Fees.
<div v-html="$t('Login.agreeTermsPolicy')"></div>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button class="submit" type="primary" @click="onSubmit">Log in</el-button>
<el-button class="submit" type="primary" @click="onSubmit">{{
$t('Login.login')
}}</el-button>
</el-form-item>
</el-form>
<div class="tip-2">
Don't have an account? <span @click.prevent="onClickRegister">Sign up</span>
</div>
<div class="tip-2" v-html="$t('Login.noAccountToSignUp')"></div>
</template>
<visible-code v-else :email="formData.email" @submit="onVerifyCode" />
<other-login />
@@ -95,14 +99,10 @@
}
const onVerifyCode = (code: string) => {
console.log(code)
router.push({ name: 'home' })
}
const onClickPrivacy = () => {}
const onClickRegister = () => {
router.push({ name: 'register' })
router.push({ name: 'mainInput' })
}
</script>
<style lang="less" scoped>
@import './style.less';
@import './less/style.less';
</style>

View File

@@ -1,14 +1,14 @@
<template>
<div class="other-login">
<div class="title">or continue with</div>
<div class="title">{{ $t('Login.orContinueWith') }}</div>
<div class="btns">
<el-button class="submit" @click="onGoogle">
<img src="@/assets/images/login/google.png" />
Sign in with Google
{{ $t('Login.googleLogin') }}
</el-button>
<el-button class="submit" @click="onWechat">
<img src="@/assets/images/login/wechat.png" />
Sign in with Wechat
{{ $t('Login.wechatLogin') }}
</el-button>
</div>
</div>
@@ -30,7 +30,7 @@
align-items: center;
justify-content: center;
font-size: 1.6rem;
font-family: Regular;
&::before,
&::after {
content: '';

View File

@@ -17,38 +17,40 @@
<img src="@/assets/images/login/elephant.png" />
<template v-if="!isVisible">
<div class="title">
<span>Register for</span>
<span>{{ $t('Login.registerFor') }}</span>
<img src="@/assets/images/logo-2.png" />
</div>
<div class="tip">A multi-agent canvas for rapid, trend driven design iteration.</div>
<div class="tip">{{ $t('Login.registerTip') }}</div>
<el-form :model="formData" :rules="ruleForm" label-position="top" ref="formRef">
<el-form-item label="Name" prop="name">
<el-input name="name" v-model="formData.name" placeholder="Enter your name" />
<el-form-item :label="$t('Login.name')" prop="name">
<el-input name="name" v-model="formData.name" :placeholder="$t('Login.enterName')" />
</el-form-item>
<el-form-item label="Password" prop="password">
<el-form-item :label="$t('Login.password')" prop="password">
<el-input
name="password"
v-model="formData.password"
placeholder="Enter your password"
:placeholder="$t('Login.enterPassword')"
type="password"
show-password
/>
</el-form-item>
<el-form-item label="Email" prop="email">
<el-input name="email" v-model="formData.email" placeholder="Enter your email" />
<el-form-item :label="$t('Login.email')" prop="email">
<el-input
name="email"
v-model="formData.email"
:placeholder="$t('Login.enterEmail')"
/>
</el-form-item>
<el-form-item prop="privacy" class="privacy">
<el-checkbox v-model="formData.privacy">
I agree to the <span @click.prevent="onClickPrivacy">Terms, Policy</span> and Fees.
<div v-html="$t('Login.agreeTermsPolicy')"></div>
</el-checkbox>
</el-form-item>
<el-form-item>
<el-button class="submit" type="primary" @click="onSubmit">Register</el-button>
<el-button class="submit" type="primary" @click="onSubmit">{{ $t('Login.register') }}</el-button>
</el-form-item>
</el-form>
<div class="tip-2">
Already have an account? <span @click.prevent="onClickLogin">Log in</span>
</div>
<div class="tip-2" v-html="$t('Login.havenAccountToLogin')"></div>
</template>
<visible-code v-else :email="formData.email" @submit="onVerifyCode" />
<other-login />
@@ -97,14 +99,10 @@
}
const onVerifyCode = (code: string) => {
console.log(code)
router.push({ name: 'home' })
}
const onClickPrivacy = () => {}
const onClickLogin = () => {
router.push({ name: 'login' })
router.push({ name: 'nuic' })
}
</script>
<style lang="less" scoped>
@import './style.less';
@import './less/style.less';
</style>

View File

@@ -1,9 +1,11 @@
import i18n from '@/lang'
const t = i18n.global.t
export const validateName = (rule, value, callback) => {
var str = ""
if (!value) {
str = 'Please input the name'
str = t('Login.pleaseInputName')
} else if (value.length < 2 || value.length > 20) {
str = 'Name length must be between 2 and 20 characters'
str = t('Login.nameLengthError', { min: 2, max: 20 })
}
callback(str ? new Error(str) : undefined)
}
@@ -11,24 +13,24 @@ export const validateEmail = (rule, value, callback) => {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\.[a-zA-Z]{2,})?$/
var str = ''
if (!value) {
str = 'Please input the email'
str = t('Login.pleaseInputEmail')
} else if (!emailRegex.test(value)) {
str = 'Please input the email again'
str = t('Login.emailFormatError')
}
callback(str ? new Error(str) : undefined)
}
export const validatePass = (rule, value, callback) => {
var str = ''
if (!value) {
str = 'Please input the password'
str = t('Login.pleaseInputPassword')
} else if (value.length < 6 || value.length > 20) {
str = 'Password length must be between 6 and 20 characters'
str = t('Login.passwordLengthError', { min: 6, max: 20 })
}
callback(str ? new Error(str) : undefined)
}
export const validatePrivacy = (rule, value, callback) => {
if (!value) {
callback(new Error('Please agree to the Terms, Policy and Fees'))
callback(new Error(t('Login.pleaseTermsPolicy')))
} else {
callback()
}

View File

@@ -1,13 +1,12 @@
<template>
<div class="visible-code">
<div class="title">Verify your email address</div>
<div class="tip">
A verification code has been sent to <span>{{ email }}</span>
</div>
<div class="title">{{ $t('Login.verifyEmail') }}</div>
<div class="tip" v-html="$t('Login.verifyCodeHasSent', { email: props.email })"></div>
<input-code @submit="onVerify" v-model="code" />
<el-button class="verify" @click="onVerify">Verify</el-button>
<p class="time" v-if="time > -1">
<span @click="onResend">Resend Code </span> in {{ timeStr }}
<el-button class="verify" @click="onVerify">{{ $t('Login.verify') }}</el-button>
<p class="time" v-if="time > 0">{{ $t('Login.resendCodeIn', { time: timeStr }) }}</p>
<p class="time" v-if="time === 0">
<span @click="onResend">{{ $t('Login.resendCode') }}</span>
</p>
</div>
</template>
@@ -67,14 +66,16 @@
font-weight: 600;
font-size: 4rem;
color: #252727;
font-family: SemiBold;
}
> .tip {
margin-top: 2rem;
font-size: 1.8rem;
color: #666;
> span {
font-family: Regular;
&::v-deep > span {
color: #252727;
font-weight: 600;
font-family: Medium;
}
}
> .input-code {
@@ -88,17 +89,20 @@
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
font-family: SemiBold;
}
> .time {
user-select: none;
margin-top: 2rem;
font-size: 1.6rem;
color: #666;
font-family: Regular;
> span {
color: #ff7a50;
text-decoration: underline;
cursor: pointer;
font-weight: 500;
font-family: Medium;
}
}
}

155
src/views/nuic/index.vue Normal file
View File

@@ -0,0 +1,155 @@
<template>
<div class="nuic background-pink">
<div class="logo">
<img src="@/assets/images/logo.png" />
<span>FiDA</span>
</div>
<div class="header">
<div class="close" @click="onClose">
<svg-icon name="close-border" size="33" />
</div>
<div
class="item"
v-for="(v, i) in list"
:key="i"
:state="active === i ? 1 : active > i ? 0 : 2"
>
<img
v-show="i === active"
src="@/assets/images/nuic/nav-active.png"
draggable="false"
/>
</div>
</div>
<component class="view" :is="list[active]" @next="onNext" />
</div>
</template>
<script setup lang="ts">
import { computed, ref, markRaw } from 'vue'
import nuic1 from './nuic-1.vue'
import nuic2 from './nuic-2.vue'
import nuic3 from './nuic-3.vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const active = computed(() => Number(route.query.index || 0))
const list = markRaw([nuic1, nuic2, nuic3])
const onClose = () => {
router.push({ name: 'mainInput' })
}
const onNext = () => {
const index = active.value + 1
if (index < list.length) {
router.push({ query: { index } })
} else {
router.push({ name: 'mainInput' })
}
}
</script>
<style lang="less" scoped>
.nuic {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
// align-items: center;
// justify-content: center;
> .logo {
position: absolute;
top: 3.8rem;
left: 3.8rem;
> img {
width: 6rem;
height: auto;
}
> span {
font-weight: 600;
font-size: 3.3rem;
font-family: SemiBold;
}
}
> .header {
margin-top: 5rem;
width: 100%;
display: flex;
justify-content: center;
align-items: flex-end;
height: 11.2rem;
> * {
margin-right: 3rem;
&:last-child {
margin-right: 0;
}
}
> .close {
margin-bottom: -0.65rem;
cursor: pointer;
}
> .item {
width: 30.2rem;
&[state='1'] {
height: 100%;
}
&[state='2'],
&[state='0'] {
height: 2rem;
border-radius: 2rem;
}
&[state='0'] {
background: linear-gradient(90deg, #ff8ca2 0%, #ffa497 49.04%, #ff8d55 100%);
}
&[state='2'] {
background-color: #dfdfdf;
}
> img {
width: 100%;
height: auto;
}
}
}
&::v-deep > .view {
margin-top: 5rem;
display: flex;
flex-direction: column;
align-items: center;
color: #252727;
text-align: center;
> .btns {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
> button {
width: 29.6rem;
height: 6rem;
border-radius: 6rem;
border: none;
outline: none;
font-size: 2.4rem;
font-weight: 600;
color: #252727;
cursor: pointer;
margin-right: 3.4rem;
background-color: #fff;
font-family: SemiBold;
&:last-child {
margin-right: 0;
}
&.skip {
color: #8d8d8d;
}
&:active {
opacity: 0.8;
}
}
}
}
}
</style>

54
src/views/nuic/nuic-1.vue Normal file
View File

@@ -0,0 +1,54 @@
<template>
<div class="nuic-1">
<img src="@/assets/images/nuic/nuic-1-bg.png" />
<p class="hi">{{ $t('Nuic.hiName', { name: 'Aaa' }) }}</p>
<p class="title" v-html="$t('Nuic.nuic1Title')"></p>
<p class="tip" v-html="$t('Nuic.nuic1Tip')"></p>
<div class="btns">
<button class="next" @click="emit('next')">{{ $t('Nuic.letsGo') }}</button>
<button class="skip" @click="onSkip">{{ $t('Nuic.skip') }}</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['next'])
const onSkip = () => {
router.push({ name: 'mainInput' })
}
</script>
<style lang="less" scoped>
.nuic-1 {
> img {
width: 44.7rem;
height: auto;
margin-bottom: 4.8rem;
}
> .hi {
font-weight: 500;
font-size: 4rem;
margin-bottom: 1rem;
}
> .title {
font-weight: 500;
font-size: 4rem;
margin-bottom: 2rem;
&::v-deep > b {
font-size: 4.8rem;
font-family: Migra-Extrabold;
font-style: italic;
}
}
> .tip {
font-weight: 400;
font-size: 2rem;
color: #585858;
margin-bottom: 8.7rem;
font-family: Regular;
}
}
</style>

106
src/views/nuic/nuic-2.vue Normal file
View File

@@ -0,0 +1,106 @@
<template>
<div class="nuic-2">
<p class="title" v-html="$t('Nuic.nuic2Title')"></p>
<div class="list">
<div v-for="v in list" :key="v.id" @click="v.active = !v.active">
<img :src="v.url" draggable="false" />
<div class="active" v-show="v.active">
<span>这是一段文字</span>
</div>
</div>
</div>
<div class="btns">
<button class="more" @click="onLoadMore">
<span>{{ $t('Nuic.loadMore') }}</span>
<div><svg-icon name="refresh-single" size="24" /></div>
</button>
<button class="next" @click="emit('next')">{{ $t('Nuic.next') }}</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['next'])
const list = ref([
{ id: 1, url: '/image/nuic/style-1.png', active: false },
{ id: 2, url: '/image/nuic/style-2.png', active: false },
{ id: 3, url: '/image/nuic/style-3.png', active: false },
{ id: 4, url: '/image/nuic/style-4.png', active: false },
{ id: 5, url: '/image/nuic/style-5.png', active: false },
{ id: 6, url: '/image/nuic/style-6.png', active: false },
{ id: 7, url: '/image/nuic/style-7.png', active: false },
{ id: 8, url: '/image/nuic/style-8.png', active: false }
])
const onLoadMore = () => {}
</script>
<style lang="less" scoped>
.nuic-2 {
> .title {
font-weight: 500;
font-size: 4rem;
margin-bottom: 6rem;
&::v-deep > b {
font-size: 4.8rem;
font-family: Migra-Extrabold;
font-style: italic;
}
}
> .list {
margin-bottom: 13rem;
display: grid;
grid-template-columns: repeat(4, 1fr);
user-select: none;
grid-gap: 3rem;
> div {
width: 21.9rem;
height: 21.9rem;
position: relative;
> img {
width: 100%;
height: 100%;
border-radius: 1.6rem;
}
> .active {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
top: 0;
left: 0;
border-radius: 1.6rem;
border: 0.4rem solid #ff945e;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 138, 140, 0.09) 68.75%,
#f98848 100%
);
display: flex;
flex-direction: column-reverse;
> span {
font-weight: 600;
font-size: 2rem;
color: #fff;
margin-bottom: 1rem;
text-shadow: 1px 1px 4.7px #d9692b;
font-family: SemiBold;
}
}
}
}
> .btns {
> .more {
display: flex;
align-items: center;
justify-content: center;
> span {
margin-right: 1.2rem;
}
}
}
}
</style>

104
src/views/nuic/nuic-3.vue Normal file
View File

@@ -0,0 +1,104 @@
<template>
<div class="nuic-3">
<p class="title" v-html="$t('Nuic.nuic3Title')"></p>
<div class="select-item">
<div class="title">{{ $t('Nuic.basedIn') }}</div>
<el-select v-model="data.based">
<el-option
class="el-select__option"
v-for="v in data.basedList"
:key="v.value"
:label="v.label"
:value="v.value"
/>
</el-select>
</div>
<div class="select-item">
<div class="title">{{ $t('Nuic.role') }}</div>
<el-select v-model="data.role">
<el-option
class="el-select__option"
v-for="v in data.roleList"
:key="v.value"
:label="v.label"
:value="v.value"
/>
</el-select>
</div>
<div class="btns">
<button class="next" @click="emit('next')">{{ $t('Nuic.allSet') }}</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, reactive } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['next'])
const data = reactive({
basedList: [
{ value: '1', label: 'Student' },
{ value: '2', label: 'Teacher' },
{ value: '3', label: 'Parent' },
{ value: '4', label: 'Other' }
],
roleList: [
{ value: '1', label: 'Student' },
{ value: '2', label: 'Teacher' },
{ value: '3', label: 'Parent' },
{ value: '4', label: 'Other' }
],
based: '',
role: ''
})
</script>
<style lang="less" scoped>
.nuic-3 {
> .title {
font-weight: 500;
font-size: 4rem;
margin-bottom: 9.8rem;
&::v-deep > b {
font-size: 4.8rem;
font-family: Migra-Extrabold;
font-style: italic;
}
}
> .select-item {
margin-bottom: 8rem;
width: 50rem;
text-align: left;
> .title {
margin-bottom: 3rem;
font-size: 3.6rem;
font-weight: 800;
color: #252727;
font-family: Migra-Extrabold;
font-style: italic;
}
> .el-select {
width: 100%;
--el-border-radius-base: 0.8rem;
&::v-deep {
font-family: Regular;
.el-select__wrapper {
min-height: auto;
height: 6rem;
font-size: 2rem;
padding: 0 1.8rem;
}
}
}
}
> .btns {
margin-top: 15.8rem;
}
}
.el-select__option {
padding: 0 1.8rem;
}
</style>