This commit is contained in:
2026-02-10 13:07:07 +08:00
49 changed files with 1904 additions and 301 deletions

View File

@@ -4,7 +4,7 @@
"tabWidth": 4,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "none",
"useTabs": true,
"trailingComma": "none",
"vueIndentScriptAndStyle": true
}
}

View File

@@ -0,0 +1,9 @@
<svg width="7" height="7" viewBox="0 0 7 7" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="6.78125" height="6.78125" fill="url(#pattern0_16_10027)"/>
<defs>
<pattern id="pattern0_16_10027" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_16_10027" transform="scale(0.0078125)"/>
</pattern>
<image id="image0_16_10027" width="128" height="128" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAMAAAD04JH5AAAAA3NCSVQICAjb4U/gAAAACXBIWXMAAAN2AAADdgF91YLMAAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAAAThQTFRF////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR/EwCQAAAGd0Uk5TAAECBAUGBwoLDA8QEhUXGRocIiUnLC03PEBCRkdJS0xOVFZXWV1hYmdobnF1enyAgYKDhoeLjY+QkpSVmp6go6anr7S1uLq7wcTGx8jKy8zN0NPV19zg5+rr7vDx8vP09vf6+/z9/seyqosAAAGbSURBVHja7dhVUwNBEATgJri7uwR3lwDBggZ3OZz5//8Aqd17DpLqKqr7KbszV9+83GYTQFEURVH+eRpiiZkqHh+Zs488dtD8RfvKTSnXN+sh+zZG9q2T7J/lcv2gluzXyZcvX758+fLly5cvX758+fLly5cvX758+fLl/y/fJsm+PdZwfbPTPK5vtkT2zaJk34Iqih/Ewo9HOZz3fz1cLHDOn+KLcFnP8IHGN7/epvjAVLjTRPGRfeC3khQfqLz3m20UH5gIX8UMio+iW7/fTvGBEV84iVB8FFz7UhfFB4Z8bYPjI//KFZ8KKT4wmNav5RTun0XPrr7K8YFN1/CQx/HRl7ajIMX7f8mr64lzfGDLNd3lcHz0+7YWjo9yfzEZ5fjAoWtcIfnwzYckPzwMA5KPVt9cxvFR8bfX8+///xB5cO29HB84cv3TJB9x98Da3/q2PZti9t0Dl7M/z0x/9ecAMePlZRzoNGqi2OcOkMQ9d4AA59wBjjHAHWAYWGb6iUwgq3l+Z+9H2d37XXbj3Wn9ha0oiqIoqeUdwmrKD1GdTTcAAAAASUVORK5CYII="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,3 @@
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.11396 0C7.52817 -2.21271e-07 7.86396 0.335786 7.86396 0.75V6.0533C7.86396 6.46751 7.52817 6.8033 7.11396 6.8033C6.69975 6.8033 6.36396 6.46751 6.36396 6.0533V2.56066L1.28033 7.64429C0.987437 7.93718 0.512563 7.93718 0.21967 7.64429C-0.0732232 7.3514 -0.0732235 6.87652 0.21967 6.58363L5.3033 1.5H1.81066C1.39645 1.5 1.06066 1.16421 1.06066 0.75C1.06066 0.335786 1.39645 -2.21271e-07 1.81066 0H7.11396Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 529 B

View File

@@ -0,0 +1,3 @@
<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.4781 2H8.9829V1.5C8.9829 1.1 8.82277 0.72043 8.5433 0.44043C8.26384 0.16043 7.88499 0 7.48575 0H4.49145C4.09221 0 3.71336 0.16043 3.43389 0.44043C3.15442 0.72043 2.9943 1.1 2.9943 1.5V2H0.49905C0.369297 2 0.239934 2.05039 0.150105 2.15039C0.0502949 2.24039 0 2.37 0 2.5C0 2.63 0.0502949 2.75961 0.150105 2.84961C0.239934 2.94961 0.369297 3 0.49905 3H0.9981V12C0.9981 12.27 1.10793 12.52 1.28759 12.71C1.47723 12.89 1.72671 13 1.9962 13H9.981C10.2505 13 10.5 12.89 10.6896 12.71C10.8693 12.52 10.9791 12.27 10.9791 12V3H11.4781C11.6079 3 11.7373 2.94961 11.8271 2.84961C11.9269 2.75961 11.9772 2.63 11.9772 2.5C11.9772 2.37 11.9269 2.24039 11.8271 2.15039C11.7373 2.05039 11.6079 2 11.4781 2ZM3.9924 1.5C3.9924 1.37 4.04269 1.24039 4.1425 1.15039C4.23233 1.05039 4.3617 1 4.49145 1H7.48575C7.6155 1 7.74486 1.05039 7.83469 1.15039C7.9345 1.24039 7.9848 1.37 7.9848 1.5V2H3.9924V1.5ZM9.981 12H1.9962V3H9.981V12ZM4.9905 5.5V9.5C4.9905 9.63 4.9402 9.75961 4.84039 9.84961C4.75056 9.94961 4.6212 10 4.49145 10C4.3617 10 4.23233 9.94961 4.1425 9.84961C4.04269 9.75961 3.9924 9.63 3.9924 9.5V5.5C3.9924 5.37 4.04269 5.24039 4.1425 5.15039C4.23233 5.05039 4.3617 5 4.49145 5C4.6212 5 4.75056 5.05039 4.84039 5.15039C4.9402 5.24039 4.9905 5.37 4.9905 5.5ZM7.9848 5.5V9.5C7.9848 9.63 7.9345 9.75961 7.83469 9.84961C7.74486 9.94961 7.6155 10 7.48575 10C7.35599 10 7.22663 9.94961 7.1368 9.84961C7.03699 9.75961 6.9867 9.63 6.9867 9.5V5.5C6.9867 5.37 7.03699 5.24039 7.1368 5.15039C7.22663 5.05039 7.35599 5 7.48575 5C7.6155 5 7.74486 5.05039 7.83469 5.15039C7.9345 5.24039 7.9848 5.37 7.9848 5.5Z" fill="#FF4747"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,3 @@
<svg width="15" height="14" viewBox="0 0 15 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.08333 1.41667C3.91503 1.41667 1.41667 3.83275 1.41667 6.72917C1.41667 7.93035 1.84139 9.04043 2.56442 9.93465C2.69502 10.0962 2.74814 10.3069 2.70973 10.511C2.64157 10.8732 2.55238 11.2273 2.44848 11.5742C2.89512 11.5055 3.3283 11.4111 3.75296 11.2905C3.92954 11.2404 4.11868 11.2607 4.28054 11.3473C5.10515 11.7883 6.06096 12.0417 7.08333 12.0417C10.2516 12.0417 12.75 9.62558 12.75 6.72917C12.75 3.83275 10.2516 1.41667 7.08333 1.41667ZM0 6.72917C0 2.97515 3.21001 0 7.08333 0C10.9567 0 14.1667 2.97515 14.1667 6.72917C14.1667 10.4832 10.9567 13.4583 7.08333 13.4583C5.92831 13.4583 4.83503 13.1953 3.8685 12.727C3.09341 12.9276 2.29663 13.0513 1.46026 13.1028C1.21968 13.1177 0.988073 13.0091 0.845512 12.8148C0.70295 12.6204 0.669005 12.3669 0.755415 12.1419C0.960947 11.6067 1.13362 11.0817 1.25408 10.5534C0.465327 9.47006 0 8.15203 0 6.72917ZM7.08333 3.89583C7.47454 3.89583 7.79167 4.21296 7.79167 4.60417V6.02083H9.20833C9.59954 6.02083 9.91667 6.33796 9.91667 6.72917C9.91667 7.12037 9.59954 7.4375 9.20833 7.4375H7.79167V8.85417C7.79167 9.24537 7.47454 9.5625 7.08333 9.5625C6.69213 9.5625 6.375 9.24537 6.375 8.85417V7.4375H4.95833C4.56713 7.4375 4.25 7.12037 4.25 6.72917C4.25 6.33796 4.56713 6.02083 4.95833 6.02083H6.375V4.60417C6.375 4.21296 6.69213 3.89583 7.08333 3.89583Z" fill="#0D0D0D"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,5 @@
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.77778 5.85C5.77778 5.49101 5.45443 5.2 5.05556 5.2C4.65668 5.2 4.33333 5.49101 4.33333 5.85V9.1C4.33333 9.45898 4.65668 9.75 5.05556 9.75C5.45443 9.75 5.77778 9.45898 5.77778 9.1V5.85Z" fill="#5A5A5A"/>
<path d="M8.66667 5.85C8.66667 5.49101 8.34332 5.2 7.94444 5.2C7.54557 5.2 7.22222 5.49101 7.22222 5.85V9.1C7.22222 9.45898 7.54557 9.75 7.94444 9.75C8.34332 9.75 8.66667 9.45898 8.66667 9.1V5.85Z" fill="#5A5A5A"/>
<path d="M6.97498 0C8.3006 0 9.45611 0.811973 9.77762 1.96941L9.95278 2.6H11.5453C11.5515 2.59993 11.5578 2.59993 11.564 2.6H12.2778C12.6766 2.6 13 2.89101 13 3.25C13 3.60898 12.6766 3.9 12.2778 3.9H12.2045L11.3977 10.677C11.2405 11.9981 10.0017 13 8.5253 13H4.4747C2.99834 13 1.75954 11.9981 1.60225 10.677L0.79547 3.9H0.722222C0.32335 3.9 0 3.60898 0 3.25C0 2.89101 0.32335 2.6 0.722222 2.6H1.43598C1.44223 2.59993 1.44846 2.59993 1.45467 2.6H3.04722L3.22238 1.96941C3.54389 0.811972 4.6994 0 6.02502 0H6.97498ZM9.40399 3.9C9.39353 3.90021 9.3831 3.9002 9.37269 3.9H3.6273C3.6169 3.9002 3.60647 3.90021 3.59601 3.9H2.24818L3.03848 10.5385C3.11712 11.1991 3.73652 11.7 4.4747 11.7H8.5253C9.26348 11.7 9.88288 11.1991 9.96152 10.5385L10.7518 3.9H9.40399ZM4.62369 2.2847L4.53611 2.6H8.46387L8.37629 2.2847C8.21554 1.70599 7.63778 1.3 6.97497 1.3H6.025C5.3622 1.3 4.78444 1.70599 4.62369 2.2847Z" fill="#5A5A5A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 1.89474C4.97439 1.89474 1.8 5.12616 1.8 9C1.8 10.6065 2.33964 12.0912 3.25832 13.2872C3.42426 13.5032 3.49176 13.785 3.44296 14.058C3.35635 14.5425 3.24302 15.0161 3.11101 15.4801C3.6785 15.3881 4.2289 15.2619 4.76847 15.1006C4.99282 15.0335 5.23315 15.0608 5.43881 15.1766C6.48655 15.7664 7.70098 16.1053 9 16.1053C13.0256 16.1053 16.2 12.8738 16.2 9C16.2 5.12616 13.0256 1.89474 9 1.89474ZM0 9C0 3.97915 4.0786 0 9 0C13.9214 0 18 3.97915 18 9C18 14.0209 13.9214 18 9 18C7.53244 18 6.14334 17.6482 4.91527 17.0218C3.93045 17.2902 2.91807 17.4555 1.85539 17.5245C1.54972 17.5444 1.25543 17.3992 1.0743 17.1393C0.89316 16.8793 0.85003 16.5403 0.959821 16.2393C1.22097 15.5235 1.44036 14.8213 1.59342 14.1148C0.591239 12.6658 0 10.903 0 9ZM9 5.21053C9.49706 5.21053 9.9 5.63468 9.9 6.1579V8.05263H11.7C12.1971 8.05263 12.6 8.47678 12.6 9C12.6 9.52322 12.1971 9.94737 11.7 9.94737H9.9V11.8421C9.9 12.3653 9.49706 12.7895 9 12.7895C8.50294 12.7895 8.1 12.3653 8.1 11.8421V9.94737H6.3C5.80294 9.94737 5.4 9.52322 5.4 9C5.4 8.47678 5.80294 8.05263 6.3 8.05263H8.1V6.1579C8.1 5.63468 8.50294 5.21053 9 5.21053Z" fill="#5A5A5A"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.64029 0C2.1068 0 2.48498 0.377023 2.48498 0.842105V2.16586C3.91988 0.823234 5.85092 0 7.97546 0C12.4073 0 16 3.58172 16 8C16 12.4183 12.4073 16 7.97546 16C3.86242 16 0.473169 12.9158 0.00582232 8.94012C-0.0484778 8.4782 0.283113 8.05986 0.746451 8.00572C1.20979 7.95159 1.62942 8.28217 1.68372 8.74409C2.05249 11.8812 4.72939 14.3158 7.97546 14.3158C11.4743 14.3158 14.3106 11.4881 14.3106 8C14.3106 4.51189 11.4743 1.68421 7.97546 1.68421C6.09992 1.68421 4.41391 2.49658 3.25326 3.78947H5.46514C5.93164 3.78947 6.30982 4.1665 6.30982 4.63158C6.30982 5.09666 5.93164 5.47368 5.46514 5.47368H1.64029C1.1847 5.47368 0.813351 5.1141 0.796223 4.66408C0.795462 4.64649 0.795254 4.62886 0.795606 4.6112V0.842105C0.795606 0.377023 1.17379 0 1.64029 0Z" fill="#5A5A5A"/>
</svg>

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,11 +1,9 @@
export default {
AlphaVersion: '2026 Alpha Version',
Login: {
login: 'Log in',
register: 'Register',
signUp: 'Sign up',
loginTo: 'Log on to <span>FiDA</span>',
loginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
Login: 'Log in',
SignUp: 'Sign up',
LoginTo: 'Log on to',
LoginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
name: 'Name',
email: 'Email',
password: 'Password',
@@ -23,7 +21,7 @@ export default {
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>`,
signUpFor: 'Sign up for <span>FiDA</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',
@@ -33,23 +31,21 @@ export default {
resendCodeIn: 'Resend Code in {time}',
orContinueWith: 'or continue with',
googleLogin: 'Sign in with Google',
wechatLogin: 'Sign in with Wechat',
indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.'
wechatLogin: 'Sign in with Wechat'
},
Nuic: {
hiName: 'Hi, {name}. This is Fiphant.',
nuic1Title: `Help him discover the <b>"YOU"</b> in your space.`,
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 <b>vibe</b> do you usually go for?`,
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>?`,
nuic3Title: `Where <b>are you based</b>? What do you <b>do</b> ?`,
basedIn: 'Based in',
role: 'Role',
allSet: 'All set!',
loadingTip: 'Were customizing your dashboard.'
allSet: 'All set!'
},
Home: {
creditsNum: 'Credits: {num}',
@@ -117,6 +113,17 @@ export default {
input: 'Input',
userRequest: 'User Request',
sketch: 'Sketch',
generateResult: 'Generate Result'
generateResult: 'Generate Result',
linearNodeTree: 'Linear Node Tree',
branchingNodeTree: 'Branching Node Tree',
restore: 'Restore',
newChat: 'New Chat',
delete: 'Delete'
},
//generateSketch
generateSketch: {
restore: 'Restore',
delete: 'Delete',
edit: 'Edit'
}
}

View File

@@ -0,0 +1,116 @@
<template>
<!-- 添加印花 -->
<div class="add-print">
<p class="label">Print</p>
<upload-file v-model="data.file" />
<p class="label">Settings</p>
<div class="settings">
<div>
<p class="label">Angle</p>
<my-input
v-model="data.setting.angle"
type="number"
after="°"
:min="-180"
:max="180"
icon="angle"
icon-size="8"
/>
</div>
<div>
<span class="label">Scale</span>
<slider
:min="1"
:max="1000"
:tipFormatter="(v) => `${v}%`"
v-model="data.setting.scale"
/>
</div>
<div>
<span class="label">Gap X</span>
<slider
:min="0"
:max="1000"
:tipFormatter="(v) => `${v}px`"
v-model="data.setting.gap.x"
/>
</div>
<div>
<span class="label">Gap Y</span>
<slider
:min="0"
:max="1000"
:tipFormatter="(v) => `${v}px`"
v-model="data.setting.gap.y"
/>
</div>
<div>
<span class="label">Offset</span>
<offset-tool v-model="data.setting.offset" :show-dish="false" />
</div>
<div class="offset">
<offset-tool v-model="data.setting.offset" :show-input="false" />
</div>
</div>
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import uploadFile from '../tools/upload-file.vue'
import myInput from '../tools/my-input.vue'
import offsetTool from '../tools/offset-tool.vue'
import slider from '../tools/slider.vue'
const data = reactive({
prompt: '',
setting: {
angle: 0,
scale: 100,
gap: {
x: 0,
y: 0
},
offset: {
x: 50,
y: 50
}
},
file: null
})
defineExpose({ data })
</script>
<style lang="less" scoped>
.add-print {
> .settings {
margin: 0 1.1rem;
> div {
display: flex;
align-items: center;
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
&.offset {
justify-content: center;
}
> .label {
width: 5.5rem;
font-size: 1rem;
}
&:not(.offset) > div {
flex: 1;
}
> .slider {
--slider-thumb-color1: #000;
--slider-thumb-color2: #eee;
}
}
}
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<!-- 颜色调色板 -->
<div class="color-palette">
<p class="label">Choose Color</p>
<div class="color-list">
<div
class="color-item"
v-for="(v, i) in data.colors"
:key="i"
:style="{ background: v }"
></div>
<div class="add" @click="addColor">
<svg-icon name="add" size="12rem" color="#fff" />
<input type="color" ref="colorInput" @change="changeColor" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted, ref } from 'vue'
const data = reactive({
colors: ['#FF4747', '#F96060', '#FFB1B1', '#FA7B7B', '#FF9090']
})
const colorInput = ref<HTMLInputElement>()
// 添加颜色
const addColor = () => {
colorInput.value?.click()
}
const changeColor = (e: Event) => {
const target = e.target as HTMLInputElement
data.colors.push(target.value)
}
defineExpose({ data })
</script>
<style lang="less" scoped>
.color-palette {
min-height: 14rem;
> .color-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
> div {
width: 3.5rem;
height: 3.5rem;
}
> .add {
border: 0.1rem solid rgba(0, 0, 0, 0.1);
background-color: rgba(0, 0, 0, 0.1);
position: relative;
> input {
position: absolute;
width: 0;
height: 0;
border: none;
padding: 0;
top: 0;
left: 0;
}
}
}
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<!-- 编辑素材 -->
<div class="edit-material">
<p class="label">Material</p>
<upload-file v-model="data.file" />
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import uploadFile from '../tools/upload-file.vue'
const data = reactive({
prompt: '',
file: null
})
defineExpose({ data })
</script>
<style lang="less" scoped>
.edit-material {
}
</style>

View File

@@ -1,12 +1,12 @@
<template>
<div class="card">
<div class="header">
<svg-icon :name="currentComponent.type" color="#fff" />
<span>{{ currentComponent.title }}</span>
<svg-icon :name="currentComponent?.type" color="#fff" />
<span>{{ currentComponent?.title }}</span>
<div class="add" @click="emit('add')"><svg-icon name="add" size="14" /></div>
</div>
<div class="body">
<component :is="currentComponent.component" ref="componentRef" />
<component :is="currentComponent?.component" ref="componentRef" />
</div>
<div class="footer">
<button @click="onGenerateClick">
@@ -19,8 +19,15 @@
<script setup lang="ts">
import { computed, ref, markRaw, onMounted } from 'vue'
import ToRealStyle from './cards/to-real-style.vue'
import SceneComposition from './cards/scene-composition.vue'
import ToRealStyle from './to-real-style.vue'
import SceneComposition from './scene-composition.vue'
import ColorPalette from './color-palette.vue'
import ToVideo from './to-video.vue'
import To3DModel from './to-3d-model.vue'
import AddPrint from './add-print.vue'
import ToCAD from './to-cad.vue'
import EditMaterial from './edit-material.vue'
const components = [
{
type: 'to-real-style',
@@ -35,33 +42,46 @@
{
type: 'color-palette',
title: 'Color Palette',
component: SceneComposition
component: ColorPalette
},
{
type: 'to-video',
title: 'To Video',
component: SceneComposition
component: ToVideo
},
{
type: 'to-3d-model',
title: 'To 3D Model',
component: SceneComposition
component: To3DModel
},
{
type: 'to-cad',
title: 'To CAD',
component: ToCAD
},
{
type: 'add-print',
title: 'Add Print',
component: SceneComposition
component: AddPrint
},
{
type: 'edit-material',
title: 'Edit Material',
component: SceneComposition
component: EditMaterial
}
]
const emit = defineEmits(['add', 'generate'])
const props = defineProps({
type: {
type: String,
type: String as () =>
| 'to-real-style'
| 'scene-composition'
| 'color-palette'
| 'to-video'
| 'to-3d-model'
| 'to-cad'
| 'add-print'
| 'edit-material',
default: 'to-real-style'
}
})
@@ -121,6 +141,20 @@
}
> .body {
padding: 1.6rem 1.3rem;
&:deep(> *) {
width: 100%;
> * {
margin-bottom: 1rem;
&:last-child {
margin-bottom: 0;
}
}
> .label {
font-size: 1.2rem;
font-family: Medium;
color: #000;
}
}
}
> .footer {
margin-bottom: 1.6rem;

View File

@@ -1,22 +1,86 @@
<template>
<!-- 场景构图 -->
<div class="scene-composition"></div>
<div class="scene-composition">
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
<p class="label">Choose Style</p>
<div class="style-list">
<div
class="item"
v-for="v in styleList"
:key="v.value"
:class="{ active: data.styles.includes(v.value) }"
@click="onClickStyle(v.value)"
>
<span class="icon"><svg-icon name="add" color="#0D0D0D" size="8" /></span>
<span class="label">{{ v.label }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, markRaw, onMounted } from 'vue'
import { useGlobalStore } from '@/stores'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const globalStore = useGlobalStore()
onMounted(() => {
globalStore.setHomeLeftNavCollapse(true)
import { computed, ref, reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
const styleList = ref([
{ label: 'Colorful', value: 'Colorful' },
{ label: 'Minimalist', value: 'Minimalist' },
{ label: 'Modernist', value: 'Modernist' },
{ label: 'Bauhaus', value: 'Bauhaus' },
{ label: 'Mintage', value: 'Mintage' },
{ label: 'Industrial', value: 'Industrial' },
{ label: 'Futuristic', value: 'Futuristic' },
{ label: 'Elegant', value: 'Elegant' },
{ label: 'Organic', value: 'Organic' },
{ label: 'Calm', value: 'Calm' },
{ label: 'Abstract', value: 'Abstract' },
{ label: 'Kitsch-core', value: 'Kitsch-core' },
{ label: 'Sophisticated', value: 'Sophisticated' },
{ label: 'Maximalism', value: 'Maximalism' },
{ label: 'Clean', value: 'Clean' },
{ label: 'Bright Colors', value: 'Bright Colors' },
{ label: 'Luxurious', value: 'Luxurious' },
{ label: 'Bold Colors', value: 'Bold Colors' },
{ label: 'Brutalism', value: 'Brutalism' }
])
const data = reactive({
prompt: '',
styles: ['Colorful', 'Modernist']
})
const onClickStyle = (value: string) => {
if (data.styles.includes(value)) {
data.styles = data.styles.filter((v) => v !== value)
} else {
data.styles.push(value)
}
}
defineExpose({ data })
</script>
<style lang="less" scoped>
.scene-composition {
width: 100%;
> .style-list {
display: flex;
flex-wrap: wrap;
gap: 0.829rem 0.55rem;
> .item {
display: flex;
align-items: center;
justify-content: center;
padding: 0.5rem 0.7rem;
font-family: Medium;
border-radius: 2rem;
font-size: 0.829rem;
border: 0.05rem solid #e4e4e7;
color: #000;
> .icon {
margin-right: 0.4rem;
}
&.active {
border-color: #0095ff;
background-color: rgba(0, 149, 255, 0.05);
}
}
}
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<!-- 转3D模型 -->
<div class="to-3d-model">
<p class="label">Image</p>
<upload-file v-model="data.file" />
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import uploadFile from '../tools/upload-file.vue'
const data = reactive({
prompt: '',
file: null
})
defineExpose({ data })
</script>
<style lang="less" scoped>
.to-3d-model {
}
</style>

View File

@@ -0,0 +1,26 @@
<template>
<!-- 转CAD -->
<div class="to-cad">
<p class="label">3D Model</p>
<upload-file v-model="data.file" />
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
</div>
</template>
<script setup lang="ts">
import { reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import uploadFile from '../tools/upload-file.vue'
const data = reactive({
prompt: '',
file: null
})
defineExpose({ data })
</script>
<style lang="less" scoped>
.to-cad {
}
</style>

View File

@@ -1,27 +1,51 @@
<template>
<!-- 转换为真实图 -->
<div class="to-real-style">
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
<div class="shortcut-list">
<div
class="item"
v-for="v in shortcutList"
:key="v.value"
@click="data.prompt = v.value"
>
{{ v.label }}
</div>
</div>
<p class="label">Size</p>
<pixel-ratio-selection v-model="data.pixelRatio" />
<upload-file v-model="data.file" />
</div>
</template>
<script setup lang="ts">
import { computed, ref, reactive, onMounted, defineExpose } from 'vue'
import myTextarea from '../my-textarea.vue'
import pixelRatioSelection from '../pixel-ratio-selection.vue'
import uploadFile from '../upload-file.vue'
import { useGlobalStore } from '@/stores'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const globalStore = useGlobalStore()
onMounted(() => {
globalStore.setHomeLeftNavCollapse(true)
})
import { computed, ref, reactive, onMounted } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import pixelRatioSelection from '../tools/pixel-ratio-selection.vue'
const shortcutList = ref([
{
label: 'Change the...',
value: 'Change the...'
},
{
label: 'Bright Colors...',
value: 'Bright Colors...'
},
{
label: 'Make the...',
value: 'Make the...'
},
{
label: 'Imagine...',
value: 'Imagine...'
},
{
label: 'Wood Materials with...',
value: 'Wood Materials with...'
}
])
const data = reactive({
prompt: '123123',
prompt: '',
pixelRatio: '1:1',
file: null
})
@@ -31,9 +55,20 @@
<style lang="less" scoped>
.to-real-style {
width: 100%;
> * {
margin-bottom: 1rem;
> .shortcut-list {
display: flex;
flex-wrap: wrap;
gap: 1rem 0.4rem;
> .item {
display: flex;
align-items: center;
padding: 0.5rem 0.3rem;
font-family: Medium;
border-radius: 0.3rem;
font-size: 1rem;
border: 0.05rem solid #e4e4e7;
background: #f0f0f0;
}
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<!-- 转视频 -->
<div class="to-video">
<p class="label">Frames</p>
<div class="frames">
<upload-file v-model="data.first_file" tip="First Frame" />
<upload-file v-model="data.last_file" tip="Last Frame" />
</div>
<p class="label">Size</p>
<pixel-ratio-selection v-model="data.pixelRatio" />
<div class="label">
<span>Aspect Ratio</span>
<span>Time</span>
</div>
<div class="select">
<el-select v-model="data.aspectRatio">
<el-option v-for="v in aspectRatioList" :key="v" :label="v" :value="v" />
</el-select>
<el-select v-model="data.time">
<el-option v-for="v in timeList" :key="v" :label="v" :value="v" />
</el-select>
</div>
<p class="label">Prompt</p>
<my-textarea v-model="data.prompt" />
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue'
import myTextarea from '../tools/my-textarea.vue'
import uploadFile from '../tools/upload-file.vue'
import pixelRatioSelection from '../tools/pixel-ratio-selection.vue'
const aspectRatioList = ref(['720p', '1080p', '1440p', '2160p', '1k', '2k'])
const timeList = ref(['5s', '10s', '15s', '20s', '30s', '60s'])
const data = reactive({
first_file: null,
last_file: null,
pixelRatio: '1:1',
aspectRatio: '720p',
time: '5s',
prompt: ''
})
defineExpose({ data })
</script>
<style lang="less" scoped>
.to-video {
> .frames {
display: flex;
gap: 0.5rem;
}
> .select,
> div.label {
display: flex;
gap: 1.3rem;
> * {
flex: 1;
}
}
> .select {
&:deep(.el-select) {
--el-select-input-font-size: 1.2rem;
.el-select__wrapper {
font-size: 1.2rem;
min-height: 0;
height: 2.8rem;
padding: 0 0.8rem;
}
.el-select__selected-item,
.el-select__input-wrapper,
.el-select__placeholder {
line-height: normal;
}
.el-select__input {
height: 2.4rem;
}
}
}
}
.el-popper{
.el-select-dropdown{
li{
padding-left: 0.8rem;
height: 3rem;
line-height: 3rem;;
font-size: 1.2rem;
}
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<div class="my-input">
<span class="decorate"></span>
<span v-show="icon" class="icon">
<svg-icon :name="icon" :size="iconSize" />
</span>
<span v-show="before" class="before">{{ before }}</span>
<input v-bind="attrs" :value="modelValue" @input="onInput" />
<span v-show="after" class="after">{{ after }}</span>
</div>
</template>
<script setup lang="ts">
import { ref, useAttrs, watch } from 'vue'
const props = defineProps({
modelValue: { type: [String, Number] },
icon: { default: '', type: String },
iconSize: { default: '10', type: [Number, String] },
before: { default: '', type: String },
after: { default: '', type: String }
})
const attrs = useAttrs()
const emit = defineEmits(['update:modelValue', 'input'])
const onInput = (e) => {
var value = e.target.value
if (attrs.type === 'number') value = Number(value)
emit('update:modelValue', value)
emit('input', value)
}
</script>
<style scoped lang="less">
.my-input {
display: flex;
align-items: center;
width: 100%;
border: 1px solid rgba(230, 230, 231, 1);
border-radius: 0.17rem;
height: 1.7rem;
padding: 0 0.4rem 0 0.2rem;
> .decorate {
width: 0.2rem;
background-color: rgba(230, 230, 231, 1);
border-radius: 0.3rem;
height: 85%;
margin-right: 0.4rem;
}
> .iconfont {
font-size: 1rem;
color: #000;
margin-right: 0.2rem;
}
> .before {
font-size: 1rem;
color: #000;
margin-right: 0.2rem;
}
> .after {
font-size: 1rem;
color: #000;
margin-left: 0.1rem;
}
> input {
font-size: 1rem;
width: 0;
flex: 1;
text-align: right;
outline: none;
border: none;
background-color: transparent;
padding: 0;
-moz-appearance: textfield; /* Firefox */
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
}
}
</style>

View File

@@ -0,0 +1,217 @@
<template>
<div class="offset-tool">
<div class="input" v-show="showInput">
<my-input v-model="left" type="number" before="X" after="%" :min="-100" :max="100" />
<my-input v-model="top" type="number" before="Y" after="%" :min="-100" :max="100" />
</div>
<div
class="dish"
@mousedown="mousedown"
@touchstart="mousedown"
ref="dishRef"
v-show="showDish"
>
<img src="/src/assets/images/icon/xyz.png" />
<span class="ball" :style="ballStyle"></span>
<span class="tip x">X: {{ left }}%</span>
<span class="tip y">Y: {{ top }}%</span>
<span class="line x"></span>
<span class="line y"></span>
<span class="line z" :style="lineZStyle"></span>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import MyInput from './my-input.vue'
const props = defineProps({
modelValue: { type: Object as () => { x: number; y: number } },
showInput: {
type: Boolean,
default: true
},
showDish: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'change', 'input'])
// 工具的实际坐标 -100 ~ 100
const top = ref(Math.round(props.modelValue.y))
const left = ref(Math.round(props.modelValue.x))
// 原点的坐标 0 ~ 100
const ballStyle = computed(() => ({
top: 50 + top.value / 2 + '%',
left: 50 + left.value / 2 + '%'
}))
watch(
() => props.modelValue.x,
(v) => (left.value = v)
)
watch(
() => props.modelValue.y,
(v) => (top.value = v)
)
const dishRef = ref<HTMLDivElement>()
const mousedown = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return
const rect = dishRef.value.getBoundingClientRect()
const X = e.clientX || (e as TouchEvent).touches[0].clientX
const Y = e.clientY || (e as TouchEvent).touches[0].clientY
var x = ((X - rect.left) / rect.width) * 100
var y = ((Y - rect.top) / rect.height) * 100
if (x < 0) x = 0
if (x > 100) x = 100
if (y < 0) y = 0
if (y > 100) y = 100
left.value = Math.round((x - 50) * 2)
top.value = Math.round((y - 50) * 2)
onInput()
}
mousemove(e)
const mouseup = () => {
onChange()
document.removeEventListener('mousemove', mousemove)
document.removeEventListener('touchmove', mousemove)
document.removeEventListener('mouseup', mouseup)
document.removeEventListener('touchend', mouseup)
}
document.addEventListener('mousemove', mousemove)
document.addEventListener('touchmove', mousemove)
document.addEventListener('mouseup', mouseup)
document.addEventListener('touchend', mouseup)
}
const onInput = () => {
const value = {
x: left.value,
y: top.value
}
emit('update:modelValue', value)
emit('input', value)
}
var changeTime: any = null
const onChange = () => {
clearTimeout(changeTime)
changeTime = setTimeout(() => {
const value = {
x: left.value,
y: top.value
}
emit('update:modelValue', value)
emit('change', value)
}, 500)
}
const lineZStyle = computed(() => ({
'--rotateZ': calculateAngle(0, 0, left.value, top.value) + 'deg',
width: calculateDistance(0, 0, left.value, top.value) / 2 + '%'
}))
// 计算角度
function calculateAngle(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1
const deltaY = y1 - y2
let angle = Math.atan2(deltaX, deltaY) * (180 / Math.PI) - 90
return angle
}
// 计算距离
function calculateDistance(x1: number, y1: number, x2: number, y2: number) {
const deltaX = x2 - x1
const deltaY = y2 - y1
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY)
return distance
}
</script>
<style scoped lang="less">
.offset-tool {
position: relative;
> .input {
display: flex;
align-items: center;
justify-content: center;
> * {
flex: 1;
margin-right: 1rem;
&:last-child {
margin-right: 0;
}
}
}
> .dish {
width: 11.5rem;
height: 11.5rem;
border: 0.1rem solid #eaeaea;
border-radius: 0.34rem;
cursor: pointer;
position: relative;
background-color: #f6f6f6;
margin-top: 2rem;
> * {
position: absolute;
pointer-events: none;
user-select: none;
}
> img {
width: 1.2rem;
height: 1.2rem;
bottom: 0.35rem;
right: 0.35rem;
}
> .ball {
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 0.85rem;
height: 0.85rem;
border: 0.1rem solid #fff;
background-color: #333;
border-radius: 50%;
box-shadow: 0px 0.068rem 0.17px 0px rgba(0, 0, 0, 0.26);
}
> .tip {
font-size: 0.85rem;
color: #000;
line-height: 2.4rem;
&.x {
top: 50%;
right: 0%;
transform: translate(100%, -50%);
padding-left: 6px;
}
&.y {
top: 0%;
left: 50%;
transform: translate(-50%, -100%);
}
}
> .line {
border-color: #d9d9d9;
border-style: dashed;
border-width: 0;
width: 0;
height: 0;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
&.x {
width: 100%;
border-top-width: 0.1rem;
}
&.y {
height: 100%;
border-left-width: 0.1rem;
}
&.z {
width: 50%;
border-top-width: 0.1rem;
border-color: #454754;
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
transform-origin: left center;
}
}
}
}
</style>

View File

@@ -0,0 +1,172 @@
<template>
<div class="slider" :disabled="disabled">
<div
class="input-range"
:style="{
'--progress': (value - props.min) / (props.max - props.min)
}"
>
<span class="tip">{{ props.tipFormatter(value) }}</span>
<input
type="range"
v-model="value"
v-bind="$attrs"
@input="onInput"
@change="onChange"
:disabled="disabled"
:min="props.min"
:max="props.max"
/>
</div>
<div class="input" v-show="isInput">
<my-input
type="number"
v-model="value"
v-bind="$attrs"
@input="onInput"
@change="onChange"
:disabled="disabled"
:min="props.min"
:max="props.max"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from 'vue'
import MyInput from './my-input.vue'
const props = defineProps({
modelValue: { type: Number },
disabled: {
type: Boolean,
default: false
},
min: {
type: Number,
default: 0
},
max: {
type: Number,
default: 100
},
tipFormatter: {
type: Function,
default: (v) => v
},
isInput: {
type: Boolean,
default: true
}
})
const emit = defineEmits(['update:modelValue', 'change', 'input'])
watch(
() => props.modelValue,
(v) => {
value.value = v
}
)
const value = ref(props.modelValue)
const onInput = () => {
if (props.disabled) return
const v = Number(value.value)
emit('update:modelValue', v)
emit('input', v)
}
const onChange = () => {
if (props.disabled) return
const v = Number(value.value)
emit('update:modelValue', v)
emit('change', v)
}
</script>
<style scoped lang="less">
.slider {
position: relative;
display: flex;
align-items: center;
--input-thumb-size: 0.8rem;
--backcolor1: var(--slider-thumb-color1, #4285f4);
--backcolor2: var(--slider-thumb-color2, rgba(0, 0, 0, 0.1));
&:hover {
> .input-range > .tip {
display: block;
}
}
> .input-range {
position: relative;
flex: 2;
display: flex;
> input {
width: 100%;
-webkit-appearance: none;
appearance: none;
height: 0.339rem;
border-radius: 0.3rem;
outline: none;
background: linear-gradient(
to right,
var(--backcolor1) 0%,
var(--backcolor1) calc(var(--progress) * 100%),
var(--backcolor2) calc(var(--progress) * 100%),
var(--backcolor2) 100%
);
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: var(--input-thumb-size);
height: var(--input-thumb-size);
border-radius: 50%;
background: var(--backcolor1); /* 蓝色滑块 */
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.1);
}
&::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
}
> .tip {
position: absolute;
font-size: 1rem;
pointer-events: none;
user-select: none;
color: #666;
top: calc(var(--input-thumb-size) / -2 - 0.35rem);
left: calc(
(100% - var(--input-thumb-size)) * var(--progress) + var(--input-thumb-size) / 2
);
transform: translate(-50%, -100%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 0.3rem 0.5rem;
border-radius: 0.4rem;
font-size: 1rem;
white-space: nowrap;
pointer-events: none;
display: none;
&::after {
content: '';
position: absolute;
top: 97%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 0.5rem solid transparent;
border-right: 0.5rem solid transparent;
border-top: 0.5rem solid rgba(0, 0, 0, 0.8);
}
}
}
> .input {
flex: 1;
margin-left: 1rem;
> input {
border-radius: 0.3rem;
width: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,97 @@
<template>
<div class="upload-file">
<div class="preview" v-if="url">
<img :src="url" @error="onChange(null)" />
<div class="close" @click="onChange(null)">
<svg-icon name="close-border" size="16" />
</div>
</div>
<div class="control" v-else>
<div class="icon"><svg-icon name="upload" size="17" /></div>
<p class="txt">{{ tip }}</p>
<button @click="onSelectFile">Select File</button>
</div>
</div>
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue'
const emit = defineEmits(['update:modelValue', 'change'])
const props = defineProps({
modelValue: { type: [File, null] },
tip: { type: String, default: 'Upload your files' }
})
const data = reactive({
file: null
})
const url = computed(() => (props.modelValue ? URL.createObjectURL(props.modelValue) : ''))
const onChange = (v) => {
emit('update:modelValue', v)
emit('change', v)
}
const onSelectFile = () => {
const input = document.createElement('input')
input.type = 'file'
input.accept = 'image/png, image/jpeg, image/jpg'
input.addEventListener('change', (e) => {
const file = e.target.files[0]
if (file) onChange(file)
})
input.click()
}
defineExpose({ data })
</script>
<style lang="less" scoped>
.upload-file {
width: 100%;
height: 9.9rem;
border-radius: 1rem;
background-color: #f0f0f0;
// padding: 0 1.7rem;
display: flex;
align-items: center;
justify-content: center;
> .control {
text-align: center;
> .txt {
margin-top: 0.6rem;
margin-bottom: 0.8rem;
font-size: 0.8rem;
color: #7c7c7c;
}
> button {
box-shadow: 0px 0.75px 0px 0px #00000005;
min-width: 3.9rem;
height: 1.3rem;
border-radius: 0.23rem;
background-color: #fff;
font-size: 0.6rem;
color: #000;
border: 0.05rem solid #d9d9d9;
cursor: pointer;
&:active {
opacity: 0.6;
}
}
}
> .preview {
width: 8rem;
height: 8rem;
position: relative;
> img {
height: 100%;
width: 100%;
object-fit: contain;
}
> .close {
position: absolute;
top: 0.01rem;
right: 0.01rem;
border-radius: 50%;
background-color: #fff;
cursor: pointer;
}
}
}
</style>

View File

@@ -1,59 +0,0 @@
<template>
<div class="upload-file">
<div class="icon"><svg-icon name="upload" size="17" /></div>
<span class="txt">Upload your files</span>
<button class="btn">Select File</button>
</div>
</template>
<script setup lang="ts">
import { reactive, defineExpose } from 'vue'
const emit = defineEmits(['update:modelValue', 'change'])
const props = defineProps({
modelValue: { type: String },
list: {
type: Array,
default: () => ['1:1', '4:3', '3:4', '16:9']
}
})
const data = reactive({})
const onChange = (v) => {
emit('update:modelValue', v)
emit('change', v)
}
defineExpose({ data })
</script>
<style lang="less" scoped>
.upload-file {
width: 100%;
height: 9.9rem;
border-radius: 1rem;
background-color: #f0f0f0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
// padding: 0 1.7rem;
> .txt {
margin-top: 0.6rem;
margin-bottom: 0.8rem;
font-size: 0.8rem;
color: #7c7c7c;
}
> button {
box-shadow: 0px 0.75px 0px 0px #00000005;
min-width: 3.9rem;
height: 1.3rem;
border-radius: 0.23rem;
background-color: #fff;
font-size: 0.6rem;
color: #000;
border: 0.05rem solid #d9d9d9;
cursor: pointer;
&:active {
opacity: 0.6;
}
}
}
</style>

View File

@@ -5,13 +5,14 @@
<card type="color-palette" />
<card type="to-video" />
<card type="to-3d-model" />
<card type="to-cad" />
<card type="add-print" />
<card type="edit-material" />
</div>
</template>
<script setup lang="ts">
import card from './components/card.vue'
import card from './components/cards/index.vue'
import { computed, ref, markRaw, onMounted } from 'vue'
import { useGlobalStore } from '@/stores'
import { useRouter, useRoute } from 'vue-router'
@@ -25,10 +26,12 @@
<style lang="less" scoped>
.canvas {
overflow-y: auto;
background-color: #fcf8f1;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
align-items: flex-start;
padding: 2rem;
gap: 2rem;
}

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, computed } from "vue";
import VersionTreeIndex from './versionTree/index.vue'
import GenerateSketch from './generateSketch/index.vue'
//const props = defineProps({
//})
//const emit = defineEmits([
@@ -13,6 +14,73 @@ const versionTreeData = ref({
return []
})
})
const generateData = ref({
list:[
{
id:'1',
type:'waiting',
},
{
id:'2',
type:'success',
img:'/img/success.png',
},
{
id:'3',
type:'success',
img:'/img/success.png',
},
{
id:'4',
type:'success',
img:'/img/success.png',
},
{
id:'5',
type:'success',
img:'/img/success.png',
},
{
id:'6',
type:'success',
img:'/img/success.png',
},
{
id:'7',
type:'success',
img:'/img/success.png',
},
{
id:'8',
type:'success',
img:'/img/success.png',
},
{
id:'9',
type:'success',
img:'/img/success.png',
},
]
})
const generateSketch = ()=>{
generateData.value.list.push(
{
id:'2',
type:'waiting',
img:'/img/success.png',
}
)
sketchRestore('2')
}
const sketchRestore = (id)=>{
generateData.value.list.forEach((item)=>{
if(item.id == id){
item.type = 'waiting'
}
})
}
onMounted(()=>{
})
onUnmounted(()=>{
@@ -22,8 +90,14 @@ const {} = toRefs(data);
</script>
<template>
<div class="homeNavBox">
<el-button type="primary" @click="versionTreeData.drawer = true">open Version Tree</el-button>
<div>
<el-button type="primary" @click="versionTreeData.drawer = true">open Version Tree</el-button>
<el-button type="primary" @click="generateSketch">Generate</el-button>
</div>
<VersionTreeIndex v-model:versionTreeData="versionTreeData" />
<div class="generateSketchBox">
<GenerateSketch v-model:generateData="generateData"></GenerateSketch>
</div>
</div>
</template>
<style lang="less" scoped>
@@ -31,5 +105,14 @@ const {} = toRefs(data);
width: 100%;
height: 100%;
position: relative;
display: flex;
> .generateSketchBox{
height: 100%;
width: 50%;
overflow-y: auto;
&::-webkit-scrollbar {
display: none;
}
}
}
</style>

View File

@@ -0,0 +1,164 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
const props = defineProps({
item: {
type: Object,
default: () => ({})
} as any,
})
const emit = defineEmits([
'sketchRestore','sketchDelete'
])
let data = reactive({
})
const morePopoverRef = ref(null)
const handleClick = ()=>{
morePopoverRef.value?.hide()
}
const sketchRestore = ()=>{
emit('sketchRestore')
handleClick()
}
const sketchDelete = ()=>{
emit('sketchDelete')
handleClick()
}
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="generateItem">
<div v-if="item.type === 'waiting'" class="waitingItem">
等待
<!-- <el-spinner style="display: inline-block;" /> -->
</div>
<div v-else-if="item.type === 'success'" class="successItem">
<img :src="item.img" alt="">
<div class="more">
<el-popover
width="10rem"
min-width="10rem"
ref="morePopoverRef"
trigger="click"
popper-class="moreBox"
placement="bottom-end"
>
<template #reference>
<span class="icon"><svg-icon name="more" size="25" /></span>
</template>
<template #default>
<div class="item" @click="sketchRestore">
<div class="icon">
<SvgIcon name="sketchRestore" size="14" />
</div>
<span>{{ $t('generateSketch.restore') }}</span>
</div>
<div class="item delete" @click="sketchDelete">
<div class="icon">
<SvgIcon name="sketchDelete" size="14" />
</div>
<span>{{ $t('generateSketch.delete') }}</span>
</div>
</template>
</el-popover>
</div>
<div class="edit" @click="$emit('edit')">
<span>{{ $t('generateSketch.edit') }}</span>
<div class="icon">
<SvgIcon name="generateSketchEdit" size="7" />
</div>
</div>
</div>
</div>
</template>
<style lang="less">
.el-popover.el-popper.moreBox{
border-radius: 1rem;
padding: .6rem .7rem;
min-width: 10rem;
.item{
display: flex;
cursor: pointer;
border-radius: .4rem;
align-items: center;
padding: .6rem .7rem;
margin-bottom: 1.2rem;
&.delete{
margin-bottom: 0rem;
color: #ff4747;
}
&:hover{
background-color: #f6f6f6;
}
> .icon{
margin-right: .7rem;
}
> span{
color: #000;
font-weight: 500;
font-size: 1.3rem;
line-height: 100%;
}
}
}
</style>
<style lang="less" scoped>
.generateItem{
width: 100%;
height: 100%;
position: relative;
> div{
position: relative;
}
> .waitingItem,.successItem{
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
> .successItem{
> .more{
position: absolute;
top: 1.2rem;
right: 1.2rem;
transition: all .3s;
border-radius: .3rem;
&:hover{
background-color: #e8e8e8;
}
.icon{
width: 2.4rem;
height: 2.4rem;
cursor: pointer;
}
}
> .edit{
position: absolute;
bottom: 1rem;
right: 1rem;
border-radius: 2rem;
border: 2px solid #e5e5e5;
background-color: #fff;
display: flex;
justify-content: center;
padding: .65rem 1.2rem;
cursor: pointer;
> span{
margin-right: .6rem;
font-weight: 500;
font-size: 1.4rem;
line-height: 100%;
}
}
}
}
</style>

View File

@@ -0,0 +1,52 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import GenerateItem from './generateItem.vue'
const props = defineProps({
generateData:{
type:Object,
default:()=>{
return {
list:[]
}
},
},
})
//const emit = defineEmits([
//])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="generateSketch">
<div v-for="item in generateData.list" :key="item.id" class="item">
<GenerateItem :item="item" />
</div>
</div>
</template>
<style lang="less" scoped>
.generateSketch{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
gap: 1.2rem;
.item{
width: calc((100% - 1.2rem * 3) / 4);
//设置比例属性 1 / 1
aspect-ratio: 1 / 1;
background-color: #fff;
border-radius: 1.6rem;
overflow: hidden;
}
}
</style>

View File

@@ -7,7 +7,7 @@
<svg-icon name="shouqi" size="24" />
</div>
</div>
<button class="create-btn">
<button class="create-btn" @click="onCreateProject">
<span class="icon"><svg-icon name="add" size="16" /></span>
<span v-show="!isCollapse" class="text">{{ $t('Home.newProject') }}</span>
</button>
@@ -104,14 +104,20 @@
name: 'Conversation Item 5'
}
])
const onHome = () => {
console.log('onHome')
const onCreateProject = () => {
router.push({ name: 'mainInput' })
}
const onHome = () => {}
const onCanvas = () => {
router.push({ name: 'canvas' })
}
const onHistory = () => {
showHistory.value = !showHistory.value
if (isCollapse.value) {
globalStore.setHomeLeftNavCollapse(false)
showHistory.value = true
} else {
showHistory.value = !showHistory.value
}
}
const onClickHistoryItem = (item: any) => {
router.push({ name: 'test', params: { id: item.id } })
@@ -129,7 +135,7 @@
}
}
</script>
<style lang="less" scoped>
.left-nav {
width: var(--left-nav-collapse-width, 30rem);

View File

@@ -36,7 +36,6 @@ const {} = toRefs(data);
<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>
@@ -91,12 +90,16 @@ const {} = toRefs(data);
flex: 1;
overflow-y: auto;
margin-left: 3rem;
scrollbar-width: thin;
scrollbar-color: #ababab #f1f5f9;
-ms-overflow-style: -ms-autohiding-scrollbar;
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
border-radius: 4px;
background: #ababab;
}
&::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 10px;
border-radius: 4px;
background: #d9d9d9;
}
}
}

View File

@@ -4,8 +4,8 @@ import VersionDetail from './versionDetail.vue'
import ChatHistory from './chatHistory.vue'
//const props = defineProps({
//})
//const emit = defineEmits([
//])
const emit = defineEmits([
])
const detailData = ref({
id:1,
versionDetail:{
@@ -30,7 +30,9 @@ defineExpose({})
<template>
<div class="detailBox">
<div class="versionDetail">
<VersionDetail :versionDetail="detailData.versionDetail"></VersionDetail>
<VersionDetail
:versionDetail="detailData.versionDetail"
></VersionDetail>
</div>
<div class="useInput">
<ChatHistory type="user"></ChatHistory>

View File

@@ -8,10 +8,13 @@ const props = defineProps({
default: () => ({})
}
})
//const emit = defineEmits([
//])
const emit = defineEmits([
'versionRestore',
'versionDelete',
])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
@@ -25,7 +28,6 @@ const {} = toRefs(data);
<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>
@@ -39,21 +41,18 @@ const {} = toRefs(data);
height: 100%;
position: relative;
> .title{
line-height: 2rem;
line-height: 3.2rem;
font-size: 1.4rem;
color: #000;
display: flex;
justify-content: space-between;
height: 2rem;
margin-bottom: 2.8rem;
height: 3.2rem;
margin-bottom: 2rem;
> .titleText{
opacity: .5;
font-weight: 600;
font-family: 'SemiBold';
}
> .icon{
color: #5a5a5a;
}
}
> .version{
font-family: 'SemiBold';

View File

@@ -2,6 +2,9 @@
import { ref, onMounted, onUnmounted, reactive, toRefs } from 'vue'
import Tree from './tree/index.vue'
import Detail from './detail/index.vue'
import { versionsList } from './tools/versionsData'
import { findAndAddChild, findAndRemoveChild } from './tools/tools'
const props = defineProps({
versionTreeData: {
type: Object,
@@ -15,12 +18,51 @@ const props = defineProps({
})
//const emit = defineEmits([
//])
const treeState = ref(true) //
const selectItem = ref({})
const treeRef = ref(null)
const treeKey = ref(0)
const treeState = ref(true)//
const openTree = () => {
treeState.value = !treeState.value
const selectItem:any = ref({})
const openTree = (state)=>{
treeState.value = state
}
const versionRestore = ()=>{
let id = ''
if(selectItem.value?.child?.length > 0){
function findMaxForYourFormat(items) {
let max = 0
for (const item of items) {
// 直接分割并取最后一部分
const parts = item.id.split('-')
const lastNumber = parseInt(parts[parts.length - 1], 10)
if (lastNumber > max) {
max = lastNumber
}
}
return max
}
id = `${selectItem.value?.id}-${findMaxForYourFormat(selectItem.value?.child) + 1}`
}else{
id = `${selectItem.value?.id}-1`
}
let addObj = {
id,
name:`V${id}`
}
findAndAddChild(versionsList, selectItem.value?.id, addObj)
selectItem.value = {...addObj}
treeKey.value++
}
const versionDelete = (versionDetail)=>{
if(!selectItem.value.id)return
findAndRemoveChild(versionsList, selectItem.value.id)
treeKey.value++
}
let data = reactive({})
@@ -47,30 +89,46 @@ const {} = toRefs(data)
</div>
</div>
</div>
<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="expandBtnBox">
<div class="btn" @click="openTree(true)">
<div class="bg left" :class="{'active':treeState}"></div>
<span>{{ $t('VersionTree.linearNodeTree') }}</span>
</div>
<div class="btn" @click="openTree(false)">
<div class="bg right" :class="{'active':!treeState}"></div>
<span>{{ $t('VersionTree.branchingNodeTree') }}</span>
</div>
</div> -->
<div class="versionTreeBox">
<div class="tree">
<Tree :treeState="treeState" v-model:selectItem="selectItem"></Tree>
<Tree
ref="treeRef"
:versionsList="versionsList"
:treeState="treeState"
v-model:selectItem="selectItem"
@versionRestore="versionRestore"
@versionDelete="versionDelete"
:key="treeKey"
></Tree>
</div>
<div class="detail">
<Detail v-model:selectItem="selectItem"></Detail>
<Detail
v-model:selectItem="selectItem"
></Detail>
</div>
</div>
</el-drawer>
</div>
</template>
<style lang="less" scoped>
.versionTree {
--border-radius: 1rem;
--treeItem-width: 5.4rem;
--treeItem-height: 5.4rem;
--treeItem-raduis: 50%;
--treeItem-border: 2px solid #000;
--treeItem-background: #ffffff;
--treeItem-active-background: #e6e6e6;
.versionTree{
--border-radius: 1rem;
--treeItem-width: 5.4rem;
--treeItem-height: 5.4rem;
--treeItem-raduis: 50%;
--treeItem-border: 2px solid #C1C1C1;
--treeItem-background: #ffffff;
--treeItem-active-background: #e6e6e6;
:deep(.versionTreeBody) {
--el-drawer-padding-primary: 0rem;
@@ -90,10 +148,80 @@ const {} = toRefs(data)
font-weight: 600;
font-family: 'SemiBold';
}
> .closeBtn {
width: 2.4rem;
height: 2.4rem;
cursor: pointer;
.versionTreeTitle{
width: 100%;
height: 8rem;
display: flex;
align-items: center;
justify-content: space-between;
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{
padding: .4rem .7rem;
border-radius: 1.1rem;
background-color: #f3f3f3;
display: flex;
margin-left: auto;
margin-top: 2.1rem;
margin-right: 3rem;
> .btn{
padding: .6rem .5rem;
position: relative;
cursor: pointer;
> .bg{
position: absolute;
background-color: #fff;
border-radius: .6rem;
width: 0;
height: 100%;
transition: all .3s;
top: 0;
&.active{
width: 100%;
}
&.left{
right: 0;
}
&.right{
left: 0;
}
}
> span{
position: relative;
font-size: 1.3rem;
line-height: 1.8rem;
font-weight: 500;
letter-spacing: -0.08px;
}
}
}
.versionTreeBox{
flex: 1;
display: flex;
overflow: hidden;
> .tree{
flex: 1;
height: 100%;
overflow: hidden;
padding: 2.1rem 0 5.4rem 2.2rem;
}
> .detail{
width: 35rem;
margin: 2.1rem 3rem 5.4rem 3.4rem;
height: calc(100% - 2.1rem - 5.4rem);
overflow: hidden;
}
}
}
.expandBtnBox {

View File

@@ -36,7 +36,10 @@ export function useLayout() {
// 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)
dagreGraph.setNode(node.id, { width: graphNode.dimensions.width || 150, height: graphNode.dimensions.height || 50 })
dagreGraph.setNode(node.id, {
width: graphNode.dimensions.width || 150,
height: graphNode.dimensions.height || 50
})
}
for (const edge of edges) {
@@ -53,11 +56,11 @@ export function useLayout() {
let targetPosition, sourcePosition
switch (layoutDirection) {
case 'BT': // 从上到下 (Top to Bottom)
targetPosition = Position.Bottom // 目标节点连接点在下方
targetPosition = Position.Bottom // 目标节点连接点在下方
sourcePosition = Position.Top // 源节点连接点在上方
break
case 'TB': // 从下到上 (Bottom to Top)
targetPosition = Position.Top // 目标节点连接点在上方
targetPosition = Position.Top // 目标节点连接点在上方
sourcePosition = Position.Bottom // 源节点连接点在下方
break
case 'LR': // 从左到右 (Left to Right)
@@ -77,10 +80,66 @@ export function useLayout() {
...node,
targetPosition,
sourcePosition,
position: { x: nodeWithPosition.x, y: nodeWithPosition.y },
position: { x: nodeWithPosition.x, y: nodeWithPosition.y }
}
})
}
return { graph, layout, previousDirection }
}
}
/**
* 递归查找指定ID的节点并添加子节点
* @param {Array} items - 要搜索的数组
* @param {string} targetId - 要查找的节点ID
* @param {Object} newChild - 要添加的新子节点
* @returns {boolean} 是否成功添加
*/
export function findAndAddChild(items, targetId, newChild) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
// 如果找到目标节点
if (item.id === targetId) {
// 初始化child数组如果不存在
if (!item.child) {
item.child = []
}
// 添加新子节点
item.child.push(newChild)
return true
}
// 递归搜索子节点
if (item.child && item.child.length > 0) {
const found = findAndAddChild(item.child, targetId, newChild)
if (found) return true
}
}
return false
}
/**
* 递归删除指定ID的节点
* @param {Array} items - 要搜索的数组
* @param {string} targetId - 要删除的节点ID
* @returns {boolean} 是否成功删除
*/
export function findAndRemoveChild(items, targetId) {
for (let i = 0; i < items.length; i++) {
const item = items[i]
// 如果找到目标节点,从当前数组中删除
if (item.id === targetId) {
items.splice(i, 1)
return true
}
// 递归搜索子节点
if (item.child && item.child.length > 0) {
const found = findAndRemoveChild(item.child, targetId)
if (found) return true
}
}
return false
}

View File

@@ -10,19 +10,15 @@ export const versionsList = [
{
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',
child:[
{
id: '1-1-1-1',
name:'V1-1-1-1',
},{
id: '1-1-1-2',
name:'V1-1-1-2',
},
]
}
]
},

View File

@@ -1,10 +1,13 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, watch } from "vue";
import { ref, onMounted, onUnmounted, reactive, toRefs, watch, nextTick } from "vue";
import view1Item from './view1Item.vue'
import view2 from './view2/index.vue'
import { versionsList } from './view2/tools/versionsData'
const props = defineProps({
versionsList: {
type: Array,
default: () => []
},
treeState:{
default:false,
},
@@ -14,11 +17,15 @@ const props = defineProps({
} as any,
})
const emit = defineEmits([
'update:selectItem'
'update:selectItem',
'versionRestore',
'versionDelete',
])
let data = reactive({
})
const view1Ref = ref(null)
const isLoad = ref(false)
const treeStateTime = ref(true)
@@ -35,13 +42,7 @@ const pushView2Item = (item)=>{
}
const treeList = ref([
{
name:'P1',
},{
name:'V1',
},{
name:'V1-1',
}
])
function traverseArray(items, callback) {
@@ -53,22 +54,44 @@ function traverseArray(items, callback) {
}
}
}
const initialize = ()=>{
isLoad.value = false
setSelectItem(versionsList[0])
treeList.value = []
traverseArray(versionsList, (item, index) => {
treeList.value.push({id: null,name:'index',})
traverseArray(props.versionsList, (item, index) => {
treeList.value.push(item)
})
isLoad.value = true
if(!props.selectItem?.id)setSelectItem(treeList.value[treeList.value.length - 1])
}
const setSelectItem = (item)=>{
console.log(item[0])
if(!item.id)return
emit('update:selectItem', {...item})
}
// 滚动到选中项
const scrollToActive = ()=>{
nextTick(() => {
const activeEl = view1Ref.value?.querySelector('.active')
console.log(activeEl)
activeEl.scrollIntoView({
behavior: 'smooth',
block: 'nearest'
})
})
}
watch(()=>treeStateTime.value,(newVal,oldVal)=>{
if((props.treeState + '') == 'false'){
// scrollToActive()
}
})
watch(()=>props.selectItem,(newVal,oldVal)=>{
// scrollToActive()
},{immediate: true})
onMounted(()=>{
initialize()
})
@@ -79,15 +102,17 @@ const {} = toRefs(data);
</script>
<template>
<div class="tree" v-show="treeStateTime" v-if="isLoad">
<div v-show="!treeState" class="box view1">
<view1Item v-for="item in treeList" :key="item.name" :item="item" @click="emit('selectItem', item)"></view1Item>
</div>
<!-- <div v-show="!treeState" class="box view1" ref="view1Ref">
<view1Item v-for="item in treeList" :key="item.name" :selectItem="props.selectItem" :item="item" @click="setSelectItem(item)"></view1Item>
</div> -->
<div v-show="treeState" class="box view2">
<view2
ref="view2Ref"
@setSelectItem="setSelectItem"
:treeList="treeList"
:selectItem="props.selectItem"
@versionRestore="()=>emit('versionRestore')"
@versionDelete="emit('versionDelete')"
></view2>
</div>
</div>

View File

@@ -4,7 +4,11 @@ const props = defineProps({
item: {
type: Object,
default: () => ({})
}
},
selectItem: {
type: Object,
default: () => {},
},
})
//const emit = defineEmits([
//])
@@ -18,7 +22,7 @@ defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="btn">
<div class="btn" :class="{'active': item.id === props.selectItem?.id}">
{{ item.name }}
</div>
</template>

View File

@@ -5,7 +5,7 @@ import { VueFlow, useVueFlow } from '@vue-flow/core'
import SpecialEdge from './speciaiEdge.vue'
import InputNode from './InputNode.vue'//主
import SecondaryNode from './secondaryNode.vue'//分支
import { useLayout } from './tools/tools'
import { useLayout } from '../../tools/tools'
const props = defineProps({
selectItem: {
type: Object,
@@ -18,6 +18,8 @@ const props = defineProps({
})
const emit = defineEmits([
'setSelectItem',
'versionRestore',
'versionDelete',
])
// 节点类型input、output、default、custom
@@ -49,14 +51,15 @@ async function layoutGraph(direction) {
}
const push = (item)=>{
if(nodes.value.length == 0){
if(!item.id){
nodes.value.push({ id: '0', type: 'InputNode', class: 'custom-node', position })
}else{
let className = `custom-node item${item.id.replace(/-/g, "_")}`
let id = item.id
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, target:id, source, type: 'smoothstep' })
}
let className = `custom-node item${item.id.replace(/-/g, "_")}`
let id = item.id
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, target:id, source, type: 'smoothstep' })
}
const initialized = ()=>{
@@ -83,6 +86,14 @@ watch(()=>props.treeList.length, (newVal, oldVal) => {
watch(()=>props.selectItem.id, (newVal, oldVal) => {
})
const versionRestore = ()=>{
emit('versionRestore')
}
const versionDelete = ()=>{
emit('versionDelete')
}
onMounted(()=>{
})
onUnmounted(()=>{
@@ -109,8 +120,21 @@ defineExpose({push})
<SpecialEdge v-bind="edgeProps" />
</template> -->
</VueFlow>
<div class="btnBox">
<div class="item" @click="versionRestore">
<div class="icon">
<SvgIcon name="versionRestore" size="12" />
</div>
<span>{{ $t('VersionTree.restore') }}</span>
</div>
<div class="item" @click="versionDelete">
<div class="icon">
<SvgIcon name="versionDelete" size="12" />
</div>
<span>{{ $t('VersionTree.delete') }}</span>
</div>
</div>
</div>
</div>
</template>
<style lang="less">
@@ -129,7 +153,7 @@ defineExpose({push})
}
:deep(.custom-node){
.node{
--vf-handle: #000;
--vf-handle: #c1c1c1;
--vf-node-color: #000;
--vf-box-shadow: #000;
font-size: 1.2rem;
@@ -155,5 +179,37 @@ defineExpose({push})
}
}
}
.btnBox{
position: absolute;
z-index: 1;
bottom: 3rem;
right: 2rem;
> .item{
width: 14.5rem;
line-height: 3.3rem;
border-radius: .8rem;
border: 1px solid #d9d9d9;
display: flex;
justify-content: center;
margin-bottom: 2rem;
background-color: #ffffff;
cursor: pointer;
&:hover{
background-color: #f5f5f5;
}
&:last-child{
margin-bottom: 0;
}
> .icon{
margin-right: .4rem;
color: rgba(0, 0, 0, 0.5);
}
> span{
font-weight: 500;
font-size: 1.4rem;
color: rgba(0, 0, 0, 0.5);
}
}
}
}
</style>

View File

@@ -20,8 +20,8 @@ const props = defineProps<{
<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" /> -->
<!-- <Handle type="source" id="Right" :position="Position.Right" />
<Handle type="target" id="Left" :position="Position.Left" /> -->
<div>{{ props.data.id }}</div>
</div>
</template>

View File

@@ -88,8 +88,8 @@
justify-content: center;
color: #252727;
}
.register > .right > .box > .title::v-deep > *,
.login > .right > .box > .title::v-deep > * {
.register > .right > .box > .title:deep(*),
.login > .right > .box > .title:deep(*) {
margin-left: 1rem;
font-family: LBold;
margin-bottom: -1rem;
@@ -102,36 +102,33 @@
color: #666;
margin-top: 0.5rem;
}
.register > .right > .box > .el-form,
.login > .right > .box > .el-form {
.register > .right > .box:deep(.el-form),
.login > .right > .box:deep(.el-form) {
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 {
.register > .right > .box:deep(.el-form) .el-form-item,
.login > .right > .box:deep(.el-form) .el-form-item {
margin-bottom: 2rem;
}
.register > .right > .box > .el-form::v-deep .el-form-item__label,
.login > .right > .box > .el-form::v-deep .el-form-item__label {
.register > .right > .box:deep(.el-form) .el-form-item__label,
.login > .right > .box:deep(.el-form) .el-form-item__label {
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 {
.register > .right > .box:deep(.el-form) .el-input,
.login > .right > .box:deep(.el-form) .el-input {
--el-input-height: 5rem;
--el-input-border-radius: 0.8rem;
--el-input-text-color: #252727;
--el-border-color: #dfdfdf;
font-size: 1.4rem;
}
.register > .right > .box > .el-form::v-deep .forgetPassword,
.login > .right > .box > .el-form::v-deep .forgetPassword {
.register > .right > .box:deep(.el-form) .forgetPassword,
.login > .right > .box:deep(.el-form) .forgetPassword {
margin-top: -1.2rem;
margin-bottom: 2rem;
font-size: 1.6rem;
@@ -140,29 +137,29 @@
cursor: pointer;
text-decoration: underline;
}
.register > .right > .box > .el-form::v-deep .privacy,
.login > .right > .box > .el-form::v-deep .privacy {
.register > .right > .box:deep(.el-form) .privacy,
.login > .right > .box:deep(.el-form) .privacy {
--el-checkbox-height: auto;
margin-bottom: 4rem;
}
.register > .right > .box > .el-form::v-deep .privacy .el-checkbox__label,
.login > .right > .box > .el-form::v-deep .privacy .el-checkbox__label {
.register > .right > .box:deep(.el-form) .privacy .el-checkbox__label,
.login > .right > .box:deep(.el-form) .privacy .el-checkbox__label {
font-size: 1.6rem;
color: #666666;
font-weight: 400;
}
.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 {
.register > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span,
.login > .right > .box:deep(.el-form) .privacy .el-checkbox__label > div > span {
text-decoration: underline;
cursor: pointer;
}
.register > .right > .box > .el-form::v-deep .el-form-item__error,
.login > .right > .box > .el-form::v-deep .el-form-item__error {
.register > .right > .box:deep(.el-form) .el-form-item__error,
.login > .right > .box:deep(.el-form) .el-form-item__error {
padding-top: 1px;
font-size: 1.4rem;
}
.register > .right > .box > .el-form::v-deep .submit,
.login > .right > .box > .el-form::v-deep .submit {
.register > .right > .box:deep(.el-form) .submit,
.login > .right > .box:deep(.el-form) .submit {
width: 100%;
height: 6rem;
background: #252727;
@@ -179,8 +176,8 @@
color: #666;
font-family: Regular;
}
.register > .right > .box > .tip-2::v-deep > span,
.login > .right > .box > .tip-2::v-deep > span {
.register > .right > .box > .tip-2:deep(span),
.login > .right > .box > .tip-2:deep(span) {
text-decoration: underline;
color: #FF7A50;
cursor: pointer;

View File

@@ -41,7 +41,10 @@
width: 100%;
display: flex;
justify-content: center;
// align-items: center;
> * {
position: relative;
z-index: 1;
}
> .tip {
position: absolute;
width: 100%;
@@ -51,6 +54,7 @@
text-align: center;
font-family: Regular;
color: #fff;
z-index: 0;
}
> .logo {
width: auto;

View File

@@ -85,7 +85,7 @@
justify-content: center;
color: #252727;
&::v-deep>* {
&:deep(*) {
margin-left: 1rem;
font-family: LBold;
margin-bottom: -1rem;
@@ -100,76 +100,73 @@
margin-top: 0.5rem;
}
>.el-form {
&:deep(.el-form) {
margin-top: 5rem;
width: 100%;
font-family: Regular;
&::v-deep {
font-family: Regular;
.el-form-item {
margin-bottom: 2rem;
}
.el-form-item {
margin-bottom: 2rem;
}
.el-form-item__label {
color: #252727;
font-size: 1.8rem;
margin-bottom: 0.8rem;
font-family: Medium;
}
.el-form-item__label {
color: #252727;
font-size: 1.8rem;
margin-bottom: 0.8rem;
font-family: Medium;
}
.el-input {
--el-input-height: 5rem;
--el-input-border-radius: 0.8rem;
--el-input-text-color: #252727;
--el-border-color: #dfdfdf;
font-size: 1.4rem;
}
.el-input {
--el-input-height: 5rem;
--el-input-border-radius: 0.8rem;
--el-input-text-color: #252727;
--el-border-color: #dfdfdf;
font-size: 1.4rem;
}
.forgetPassword {
margin-top: -1.2rem;
margin-bottom: 2rem;
font-size: 1.6rem;
text-align: right;
color: #666666;
cursor: pointer;
text-decoration: underline;
}
.forgetPassword {
margin-top: -1.2rem;
margin-bottom: 2rem;
.privacy {
--el-checkbox-height: auto;
margin-bottom: 4rem;
.el-checkbox__label {
font-size: 1.6rem;
text-align: right;
color: #666666;
cursor: pointer;
text-decoration: underline;
}
font-weight: 400;
.privacy {
--el-checkbox-height: auto;
margin-bottom: 4rem;
.el-checkbox__label {
font-size: 1.6rem;
color: #666666;
font-weight: 400;
>div {
>span {
text-decoration: underline;
cursor: pointer;
}
>div {
>span {
text-decoration: underline;
cursor: pointer;
}
}
}
}
.el-form-item__error {
padding-top: 1px;
font-size: 1.4rem;
}
.el-form-item__error {
padding-top: 1px;
font-size: 1.4rem;
}
.submit {
width: 100%;
height: 6rem;
background: #252727;
font-size: 2rem;
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
font-family: SemiBold;
}
.submit {
width: 100%;
height: 6rem;
background: #252727;
font-size: 2rem;
border-radius: 0.8rem;
color: #fff;
font-weight: 600;
font-family: SemiBold;
}
}
@@ -179,7 +176,7 @@
color: #666;
font-family: Regular;
&::v-deep>span {
&:deep(span) {
text-decoration: underline;
color: #FF7A50;
cursor: pointer;

View File

@@ -73,7 +73,7 @@
font-size: 1.8rem;
color: #666;
font-family: Regular;
&::v-deep > span {
&:deep(span) {
color: #252727;
font-family: Medium;
}

View File

@@ -110,7 +110,7 @@
}
}
}
&::v-deep > .view {
&:deep(.view) {
margin-top: 5rem;
display: flex;
flex-direction: column;

View File

@@ -37,7 +37,7 @@
font-weight: 500;
font-size: 4rem;
margin-bottom: 2rem;
&::v-deep > b {
&:deep(b) {
font-size: 4.8rem;
font-family: MBold;
}

View File

@@ -43,7 +43,7 @@
font-weight: 500;
font-size: 4rem;
margin-bottom: 6rem;
&::v-deep > b {
&:deep(b) {
font-size: 4.8rem;
font-family: MBold;
}

View File

@@ -61,7 +61,7 @@
font-weight: 500;
font-size: 4rem;
margin-bottom: 9.8rem;
&::v-deep > b {
&:deep(b) {
font-size: 4.8rem;
font-family: MBold;
}
@@ -80,7 +80,7 @@
> .el-select {
width: 100%;
--el-border-radius-base: 0.8rem;
&::v-deep {
&:deep {
font-family: Regular;
.el-select__wrapper {
min-height: auto;