Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/FiDA_Front
This commit is contained in:
@@ -39,3 +39,16 @@ export const deleteDepthCanvas = (id: string) => {
|
||||
loading: true,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分隔后图片
|
||||
* @param data 分隔参数数据
|
||||
* @returns 分隔后图片数据
|
||||
*/
|
||||
export const getSegAnythingImage = (data: object) => {
|
||||
return request({
|
||||
url: `/api/python/segAnything`,
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -133,3 +133,18 @@ export const getAvatarLimit = () => {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户风格图片列表
|
||||
* @param params 获取用户风格图片列表的参数
|
||||
* @param params.pageNum 页码
|
||||
* @param params.pageSize 每页数量
|
||||
* @returns 用户风格图片列表
|
||||
*/
|
||||
export const GetUserStyleImages = (params) => {
|
||||
return request({
|
||||
url: '/api/user/style/images',
|
||||
method: 'get',
|
||||
params,
|
||||
loading: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -96,3 +96,10 @@ body,
|
||||
--el-color-primary-dark-2: #565656;
|
||||
/* 深灰色(加深20%) */
|
||||
}
|
||||
.mini-scrollbar::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
}
|
||||
.mini-scrollbar::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.4rem;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
@@ -105,3 +105,15 @@ body,
|
||||
--el-color-primary-light-9: #e3e3e3; /* 极浅的灰色(混合60%白) */
|
||||
--el-color-primary-dark-2: #565656; /* 深灰色(加深20%) */
|
||||
}
|
||||
|
||||
|
||||
// 迷你滚动条
|
||||
.mini-scrollbar {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.4rem;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.4rem;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
3
src/assets/icons/clipAdd.svg
Normal file
3
src/assets/icons/clipAdd.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11 0C12.0125 0 12.8333 0.820811 12.8333 1.83333V9.16667H20.1667C21.1792 9.16667 22 9.98748 22 11C22 12.0125 21.1792 12.8333 20.1667 12.8333H12.8333V20.1667C12.8333 21.1792 12.0125 22 11 22C9.98748 22 9.16667 21.1792 9.16667 20.1667V12.8333H1.83333C0.820811 12.8333 0 12.0125 0 11C0 9.98748 0.820811 9.16667 1.83333 9.16667H9.16667V1.83333C9.16667 0.820811 9.98748 0 11 0Z" fill="#0D0D0D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 503 B |
3
src/assets/icons/clipMinus.svg
Normal file
3
src/assets/icons/clipMinus.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="22" height="4" viewBox="0 0 22 4" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 2C0 0.89543 0.820811 0 1.83333 0H20.1667C21.1792 0 22 0.89543 22 2C22 3.10457 21.1792 4 20.1667 4H1.83333C0.820811 4 0 3.10457 0 2Z" fill="#0D0D0D"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 263 B |
3
src/assets/icons/dc/create.svg
Normal file
3
src/assets/icons/dc/create.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 0C5.41421 0 5.75 0.335786 5.75 0.75V4.25H9.25C9.66421 4.25 10 4.58579 10 5C10 5.41421 9.66421 5.75 9.25 5.75H5.75V9.25C5.75 9.66421 5.41421 10 5 10C4.58579 10 4.25 9.66421 4.25 9.25V5.75H0.75C0.335786 5.75 0 5.41421 0 5C0 4.58579 0.335786 4.25 0.75 4.25H4.25V0.75C4.25 0.335786 4.58579 0 5 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 424 B |
3
src/assets/icons/dc/reset.svg
Normal file
3
src/assets/icons/dc/reset.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.02518 0C1.31675 0 1.55311 0.23564 1.55311 0.526316V1.35367C2.44992 0.514522 3.65683 0 4.98466 0C7.75456 0 10 2.23858 10 5C10 7.76142 7.75456 10 4.98466 10C2.41401 10 0.295731 8.07235 0.00363895 5.58758C-0.0302986 5.29888 0.176946 5.03741 0.466532 5.00358C0.756118 4.96974 1.01839 5.17635 1.05232 5.46505C1.28281 7.42573 2.95587 8.94737 4.98466 8.94737C7.17142 8.94737 8.94414 7.18007 8.94414 5C8.94414 2.81993 7.17142 1.05263 4.98466 1.05263C3.81245 1.05263 2.7587 1.56036 2.03329 2.36842H3.41571C3.70728 2.36842 3.94364 2.60406 3.94364 2.89474C3.94364 3.18541 3.70728 3.42105 3.41571 3.42105H1.02518C0.740438 3.42105 0.508345 3.19631 0.497639 2.91505C0.497164 2.90406 0.497034 2.89304 0.497254 2.882V0.526316C0.497254 0.23564 0.733616 0 1.02518 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 880 B |
@@ -1,3 +1,3 @@
|
||||
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66667 3.33275V2.08333C1.66667 1.6231 1.29357 1.25 0.833333 1.25C0.373096 1.25 0 1.6231 0 2.08333V5.83333C0 6.29357 0.373096 6.66667 0.833333 6.66667H4.16667C4.6269 6.66667 5 6.29357 5 5.83333C5 5.3731 4.6269 5 4.16667 5H2.55837C3.712 3.00589 5.86695 1.66667 8.33333 1.66667C12.0152 1.66667 15 4.65143 15 8.33333C15 12.0152 12.0152 15 8.33333 15C5.16166 15 2.5055 12.7843 1.83185 9.81559C1.73001 9.36677 1.2836 9.08548 0.834775 9.18733C0.385947 9.28917 0.104662 9.73558 0.206506 10.1844C1.04863 13.8956 4.36638 16.6667 8.33333 16.6667C12.9357 16.6667 16.6667 12.9357 16.6667 8.33333C16.6667 3.73096 12.9357 0 8.33333 0C5.60633 0 3.18634 1.30996 1.66667 3.33275ZM8.33333 3.33333C8.79357 3.33333 9.16667 3.70643 9.16667 4.16667V8.33333C9.16667 8.55435 9.07887 8.76631 8.92259 8.92259L6.83926 11.0059C6.51382 11.3314 5.98618 11.3314 5.66074 11.0059C5.33531 10.6805 5.33531 10.1528 5.66074 9.82741L7.5 7.98816V4.16667C7.5 3.70643 7.8731 3.33333 8.33333 3.33333Z" fill="black" fill-opacity="0.75"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.66667 3.33275V2.08333C1.66667 1.6231 1.29357 1.25 0.833333 1.25C0.373096 1.25 0 1.6231 0 2.08333V5.83333C0 6.29357 0.373096 6.66667 0.833333 6.66667H4.16667C4.6269 6.66667 5 6.29357 5 5.83333C5 5.3731 4.6269 5 4.16667 5H2.55837C3.712 3.00589 5.86695 1.66667 8.33333 1.66667C12.0152 1.66667 15 4.65143 15 8.33333C15 12.0152 12.0152 15 8.33333 15C5.16166 15 2.5055 12.7843 1.83185 9.81559C1.73001 9.36677 1.2836 9.08548 0.834775 9.18733C0.385947 9.28917 0.104662 9.73558 0.206506 10.1844C1.04863 13.8956 4.36638 16.6667 8.33333 16.6667C12.9357 16.6667 16.6667 12.9357 16.6667 8.33333C16.6667 3.73096 12.9357 0 8.33333 0C5.60633 0 3.18634 1.30996 1.66667 3.33275ZM8.33333 3.33333C8.79357 3.33333 9.16667 3.70643 9.16667 4.16667V8.33333C9.16667 8.55435 9.07887 8.76631 8.92259 8.92259L6.83926 11.0059C6.51382 11.3314 5.98618 11.3314 5.66074 11.0059C5.33531 10.6805 5.33531 10.1528 5.66074 9.82741L7.5 7.98816V4.16667C7.5 3.70643 7.8731 3.33333 8.33333 3.33333Z" fill="#0D0D0D"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -86,7 +86,7 @@
|
||||
width: 46.69rem;
|
||||
height: 56.6rem;
|
||||
flex-shrink: 0;
|
||||
// background-color: #fff;
|
||||
background-color: #fff;
|
||||
box-shadow: 0px 19.44px 27.22px 0px #0000000d;
|
||||
border: 1px solid;
|
||||
border-image-source: linear-gradient(
|
||||
|
||||
@@ -10,7 +10,14 @@
|
||||
<span class="icon"><svg-icon :name="item.name" size="16" /></span>
|
||||
<span class="label">{{ item.label }}</span>
|
||||
</div>
|
||||
<button @click="onCreate">创建</button>
|
||||
<button @click="onCreate">
|
||||
<span class="icon"><svg-icon name="dc-create" size="12" /></span>
|
||||
<span class="text">{{ $t('DepthCanvas.create') }}</span>
|
||||
</button>
|
||||
<button @click="onReset">
|
||||
<span class="icon"><svg-icon name="dc-reset" size="12" /></span>
|
||||
<span class="text">{{ $t('DepthCanvas.reset') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
<brush-control-panel v-if="show" :currentTool="currentTool2" style="top: 14rem" />
|
||||
@@ -21,6 +28,8 @@
|
||||
import depthSlider from './tools/depth-slider.vue'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
import brushControlPanel from './brush-control-panel.vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps({
|
||||
currentTool: { required: true, type: [String, null] }
|
||||
@@ -37,22 +46,22 @@
|
||||
{
|
||||
type: OperationType.AISELECT_ADD,
|
||||
name: 'dc-add_sb',
|
||||
label: 'Add'
|
||||
label: t('DepthCanvas.add')
|
||||
},
|
||||
{
|
||||
type: OperationType.AISELECT_REMOVE,
|
||||
name: 'dc-remove_sb',
|
||||
label: 'Remove'
|
||||
label: t('DepthCanvas.remove')
|
||||
},
|
||||
{
|
||||
type: OperationType.AISELECT_DRAW,
|
||||
name: 'dc-brush_sb',
|
||||
label: 'Brush'
|
||||
label: t('DepthCanvas.brush')
|
||||
},
|
||||
{
|
||||
type: OperationType.AISELECT_ERASER,
|
||||
name: 'dc-erase_sb',
|
||||
label: 'Erase'
|
||||
label: t('DepthCanvas.erase')
|
||||
}
|
||||
])
|
||||
const onClickItem = (type: string) => {
|
||||
@@ -61,6 +70,9 @@
|
||||
const onCreate = () => {
|
||||
stateManager.aiSelectboxToolManager.createSelectbox()
|
||||
}
|
||||
const onReset = () => {
|
||||
stateManager.aiSelectboxToolManager.resetDemoObject()
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
// 淡入淡出动画
|
||||
@@ -108,5 +120,22 @@
|
||||
background: rgba(235, 235, 235, 0.9);
|
||||
}
|
||||
}
|
||||
> button {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 2.5rem;
|
||||
background-color: rgba(13, 13, 13, 1);
|
||||
color: #fff;
|
||||
font-size: 1.2rem;
|
||||
border-radius: 0.4rem;
|
||||
padding: 0 0.8rem;
|
||||
gap: 0.8rem;
|
||||
&:active {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
</template>
|
||||
<button class="export" @click="emit('export')">
|
||||
<span class="icon"><svg-icon name="export" size="12" /></span>
|
||||
<span class="text">Export</span>
|
||||
<span class="text">{{ $t('DepthCanvas.export') }}</span>
|
||||
</button>
|
||||
<!-- <button class="export" @click="emit('export-local')">
|
||||
<span class="text">保存本地</span>
|
||||
@@ -40,7 +40,7 @@
|
||||
</button> -->
|
||||
<button class="workbench" @click="onWorkbench">
|
||||
<span class="icon"><svg-icon name="dc-workbench" size="20" /></span>
|
||||
<span class="text">Workbench</span>
|
||||
<span class="text">{{ $t('DepthCanvas.save') }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -49,6 +49,8 @@
|
||||
import { ref, inject, computed } from 'vue'
|
||||
import { exportCanvasToImage } from '../tools/exportMethod'
|
||||
import { OperationType, BlendMode } from '../tools/layerHelper'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
zoom: { default: 1, type: Number },
|
||||
step: { default: 0.1, type: Number }
|
||||
@@ -86,37 +88,37 @@
|
||||
child: [
|
||||
{
|
||||
name: OperationType.RECTANGLE,
|
||||
label: 'Rectangle',
|
||||
label: t('DepthCanvas.rectangle'),
|
||||
icon: 'dc-rectangle',
|
||||
iconSize: 13
|
||||
},
|
||||
{
|
||||
name: OperationType.LINE,
|
||||
label: 'Line',
|
||||
label: t('DepthCanvas.line'),
|
||||
icon: 'dc-line',
|
||||
iconSize: 10
|
||||
},
|
||||
{
|
||||
name: OperationType.ARROW,
|
||||
label: 'Arrow',
|
||||
label: t('DepthCanvas.arrow'),
|
||||
icon: 'dc-arrow',
|
||||
iconSize: 11
|
||||
},
|
||||
{
|
||||
name: OperationType.ELLIPSE,
|
||||
label: 'Ellipse',
|
||||
label: t('DepthCanvas.ellipse'),
|
||||
icon: 'dc-ellipse',
|
||||
iconSize: 15
|
||||
},
|
||||
{
|
||||
name: OperationType.TRIANGLE,
|
||||
label: 'Polygon',
|
||||
label: t('DepthCanvas.triangle'),
|
||||
icon: 'dc-triangle',
|
||||
iconSize: 14
|
||||
},
|
||||
{
|
||||
name: OperationType.STAR,
|
||||
label: 'Star',
|
||||
label: t('DepthCanvas.star'),
|
||||
icon: 'dc-star',
|
||||
iconSize: 15
|
||||
}
|
||||
|
||||
@@ -1,64 +1,153 @@
|
||||
<template>
|
||||
<div class="basic-info">
|
||||
<div class="basic-info h">
|
||||
<div>
|
||||
<span class="label">X:</span>
|
||||
<span class="value">{{ object.left }}</span>
|
||||
<div class="title">{{ $t('DepthCanvas.position') }}</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<depth-input
|
||||
before="X"
|
||||
type="number"
|
||||
v-model="data.left"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<depth-input
|
||||
before="Y"
|
||||
type="number"
|
||||
v-model="data.top"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<depth-input
|
||||
before="W"
|
||||
type="number"
|
||||
v-model="data.width"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<depth-input
|
||||
before="H"
|
||||
type="number"
|
||||
v-model="data.height"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Y:</span>
|
||||
<span class="value">{{ object.top }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Width:</span>
|
||||
<span class="value">{{ object.width }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Height:</span>
|
||||
<span class="value">{{ object.height }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">缩放X:</span>
|
||||
<span class="value">{{ object.scaleX }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">缩放Y:</span>
|
||||
<span class="value">{{ object.scaleY }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="label">Angle:</span>
|
||||
<span class="value">{{ object.angle }}</span>
|
||||
<div class="title">{{ $t('DepthCanvas.scale') }}</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<depth-input
|
||||
type="number"
|
||||
before="X"
|
||||
after="%"
|
||||
v-model="data.scaleX"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<depth-input
|
||||
type="number"
|
||||
before="Y"
|
||||
after="%"
|
||||
v-model="data.scaleY"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, watch, onMounted } from 'vue'
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount, reactive, watch } from 'vue'
|
||||
import DepthInput from '../tools/depth-input.vue'
|
||||
const objectManager = inject('objectManager') as any
|
||||
const stateManager = inject('stateManager') as any
|
||||
const props = defineProps({
|
||||
object: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
onMounted(() => {})
|
||||
const id = computed(() => props.object.info.id)
|
||||
|
||||
const data = reactive({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1
|
||||
})
|
||||
const updateData = async () => {
|
||||
await nextTick()
|
||||
data.top = Math.round(props.object.top)
|
||||
data.left = Math.round(props.object.left)
|
||||
data.width = Math.round(props.object.width)
|
||||
data.height = Math.round(props.object.height)
|
||||
data.scaleX = Math.round(props.object.scaleX * 100)
|
||||
data.scaleY = Math.round(props.object.scaleY * 100)
|
||||
}
|
||||
updateData()
|
||||
watch(() => props.object, updateData)
|
||||
|
||||
const onInpot = () => setPriority(false)
|
||||
const onChange = () => setPriority(true)
|
||||
const setPriority = (isRecord: boolean) => {
|
||||
const options = {
|
||||
...data,
|
||||
scaleX: data.scaleX / 100,
|
||||
scaleY: data.scaleY / 100
|
||||
}
|
||||
objectManager.updateProperty(id.value, options, isRecord)
|
||||
}
|
||||
|
||||
stateManager.event.add('canvas:undo', updateData)
|
||||
stateManager.event.add('canvas:redo', updateData)
|
||||
onBeforeUnmount(() => {
|
||||
stateManager.event.remove('canvas:undo', updateData)
|
||||
stateManager.event.remove('canvas:redo', updateData)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.basic-info {
|
||||
--details-item-margin-bottom: 1.6rem;
|
||||
> div {
|
||||
padding: 0.8rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
> .label {
|
||||
font-size: 1.6rem;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
min-width: 7rem;
|
||||
text-align: right;
|
||||
margin-right: 0.8rem;
|
||||
}
|
||||
> .value {
|
||||
font-size: 1.6rem;
|
||||
color: #333;
|
||||
> .content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> div {
|
||||
flex: 1;
|
||||
margin-right: 3rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
--depth-input-height: 2.4rem;
|
||||
--depth-input-bg-color: rgba(249, 249, 250, 1);
|
||||
--depth-input-border-color: rgba(230, 230, 231, 1);
|
||||
--depth-input-decorate-color: rgba(69, 71, 84, 0.1);
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
> .label {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div class="fill-repeat h">
|
||||
<div>
|
||||
<div class="title">Image</div>
|
||||
<div class="title">{{ $t('DepthCanvas.image') }}</div>
|
||||
<div class="content">
|
||||
<img :src="object.info.fill.source" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="title">Sttings</div>
|
||||
<div class="title">{{ $t('DepthCanvas.settings') }}</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<div class="label">Rotation</div>
|
||||
<div class="label">{{ $t('DepthCanvas.rotation') }}</div>
|
||||
<div class="value">
|
||||
<depth-input
|
||||
icon="dc-angle"
|
||||
@@ -23,7 +23,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Scale</div>
|
||||
<div class="label">{{ $t('DepthCanvas.scale') }}</div>
|
||||
<div class="value">
|
||||
<depth-slider
|
||||
v-model="scale"
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Opacity</div>
|
||||
<div class="label">{{ $t('DepthCanvas.opacity') }}</div>
|
||||
<div class="value">
|
||||
<depth-slider
|
||||
v-model="opacity"
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Gap X</div>
|
||||
<div class="label">{{ $t('DepthCanvas.gapX') }}</div>
|
||||
<div class="value">
|
||||
<depth-slider
|
||||
v-model="gapX"
|
||||
@@ -62,7 +62,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Gap Y</div>
|
||||
<div class="label">{{ $t('DepthCanvas.gapY') }}</div>
|
||||
<div class="value">
|
||||
<depth-slider
|
||||
v-model="gapY"
|
||||
@@ -75,7 +75,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Offset</div>
|
||||
<div class="label">{{ $t('DepthCanvas.offset') }}</div>
|
||||
<div class="value">
|
||||
<depth-offset-tool
|
||||
v-model="offset"
|
||||
@@ -99,7 +99,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount } from 'vue'
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount, watch } from 'vue'
|
||||
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||
import DepthSlider from '../tools/depth-slider.vue'
|
||||
import DepthInput from '../tools/depth-input.vue'
|
||||
@@ -134,6 +134,7 @@
|
||||
opacity.value = Math.round(props.object.opacity * 100)
|
||||
}
|
||||
updateData()
|
||||
watch(() => props.object, updateData)
|
||||
|
||||
const inputFillTransform = () => setFillTransform(false)
|
||||
const changeFillTransform = () => setFillTransform(true)
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
<div class="details-panel">
|
||||
<div class="header" @click="show = !show">
|
||||
<span class="icon"><svg-icon name="dc-details_edit" size="17" /></span>
|
||||
<span class="title">Edit Details</span>
|
||||
<span class="title">{{ $t('DepthCanvas.editDetails') }}</span>
|
||||
<span class="arrow" :class="{ show }">
|
||||
<svg-icon name="dc-down_arrow2" size="10" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="content" v-if="isShow" v-show="show">
|
||||
<!-- <basic-info :object="activeObject" /> -->
|
||||
<div class="content mini-scrollbar" v-if="isShow" v-show="show">
|
||||
<basic-info :object="activeObject" />
|
||||
<fill-repeat :object="activeObject" v-if="isRepeat" />
|
||||
<shape-setting :object="activeObject" v-if="isShape && !isRepeat" />
|
||||
</div>
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, watch, onMounted } from 'vue'
|
||||
import BasicInfo from './basic-info.vue'
|
||||
import FillRepeat from './fill-repeat.vue'
|
||||
import ShapeSetting from './shape-setting.vue'
|
||||
const props = defineProps({})
|
||||
@@ -27,10 +28,11 @@
|
||||
const layers = computed(() => layerManager.layers.value)
|
||||
const activeObject = ref(null)
|
||||
|
||||
const shapes = ['rect', 'line', 'path', 'triangle', 'polygon', 'ellipse']
|
||||
const shapes = ['rect', 'path', 'triangle', 'polygon', 'ellipse']
|
||||
const isImage = computed(() => activeObject.value?.type === 'image')
|
||||
const isShape = computed(() => shapes.includes(activeObject.value?.type))
|
||||
const isRepeat = computed(() => activeObject.value?.fill?.repeat === 'repeat')
|
||||
const isShow = computed(() => isRepeat.value || isShape.value)
|
||||
const isShow = computed(() => isImage.value || isRepeat.value || isShape.value)
|
||||
|
||||
const updateActiveObject = () => {
|
||||
const layer = layerManager.getActiveLayer()
|
||||
@@ -49,7 +51,7 @@
|
||||
top: 2.2rem;
|
||||
right: 3rem;
|
||||
width: 28.8rem;
|
||||
max-height: 80%;
|
||||
max-height: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.6rem;
|
||||
@@ -87,6 +89,12 @@
|
||||
padding: 1.6rem;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
> * {
|
||||
margin-bottom: 1.6rem;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&:deep(> div) {
|
||||
> div {
|
||||
margin-bottom: var(--details-item-margin-bottom, 1.6rem);
|
||||
|
||||
@@ -1,79 +1,10 @@
|
||||
<template>
|
||||
<div class="shape-setting h">
|
||||
<div>
|
||||
<div class="title">Position</div>
|
||||
<div class="title">{{ $t('DepthCanvas.appearance') }}</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<depth-input
|
||||
before="X"
|
||||
type="number"
|
||||
v-model="data.left"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<depth-input
|
||||
before="Y"
|
||||
type="number"
|
||||
v-model="data.top"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<depth-input
|
||||
before="W"
|
||||
type="number"
|
||||
v-model="data.width"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<depth-input
|
||||
before="H"
|
||||
type="number"
|
||||
v-model="data.height"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="title">Scale</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<depth-input
|
||||
type="number"
|
||||
before="X"
|
||||
after="%"
|
||||
v-model="data.scaleX"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<depth-input
|
||||
type="number"
|
||||
before="Y"
|
||||
after="%"
|
||||
v-model="data.scaleY"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="title">Appearance</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<span class="label">Opacity</span>
|
||||
<span class="label">{{ $t('DepthCanvas.opacity') }}</span>
|
||||
<depth-input
|
||||
type="number"
|
||||
after="%"
|
||||
@@ -86,7 +17,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div v-if="object.type === 'rect'">
|
||||
<span class="label">Corner Radius</span>
|
||||
<span class="label">{{ $t('DepthCanvas.cornerRadius') }}</span>
|
||||
<depth-input
|
||||
type="number"
|
||||
v-model="data.radius"
|
||||
@@ -96,12 +27,32 @@
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-else-if="object.type === 'path'">
|
||||
<span class="label">{{ $t('DepthCanvas.strokeWidth') }}</span>
|
||||
<depth-input
|
||||
type="number"
|
||||
v-model="data.strokeWidth"
|
||||
min="0"
|
||||
step="1"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div>
|
||||
<span class="label">Color</span>
|
||||
<span class="label">{{ $t('DepthCanvas.color') }}</span>
|
||||
<depth-input
|
||||
v-if="object.type === 'path'"
|
||||
type="color"
|
||||
v-model="data.stroke"
|
||||
after="%"
|
||||
@input="onInpot"
|
||||
@change="onChange"
|
||||
/>
|
||||
<depth-input
|
||||
v-else
|
||||
type="color"
|
||||
v-model="data.fill"
|
||||
after="%"
|
||||
@@ -116,10 +67,6 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject, computed, nextTick, onBeforeUnmount, reactive, watch } from 'vue'
|
||||
import { ElColorPicker } from 'element-plus'
|
||||
import { getTransformScaleAngle } from '../../manager/ObjectManager'
|
||||
import DepthOffsetTool from '../tools/depth-offset-tool.vue'
|
||||
import DepthSlider from '../tools/depth-slider.vue'
|
||||
import DepthInput from '../tools/depth-input.vue'
|
||||
const objectManager = inject('objectManager') as any
|
||||
const stateManager = inject('stateManager') as any
|
||||
@@ -132,32 +79,20 @@
|
||||
const id = computed(() => props.object.info.id)
|
||||
|
||||
const data = reactive({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
scaleX: 1,
|
||||
scaleY: 1,
|
||||
opacity: 100,
|
||||
radius: 0,
|
||||
|
||||
fill: ''
|
||||
// stroke: '',
|
||||
// strokeWidth: 0
|
||||
fill: '',
|
||||
stroke: '',
|
||||
strokeWidth: 0
|
||||
})
|
||||
const updateData = async () => {
|
||||
await nextTick()
|
||||
data.top = Math.round(props.object.top)
|
||||
data.left = Math.round(props.object.left)
|
||||
data.width = Math.round(props.object.width)
|
||||
data.height = Math.round(props.object.height)
|
||||
data.scaleX = Math.round(props.object.scaleX * 100)
|
||||
data.scaleY = Math.round(props.object.scaleY * 100)
|
||||
data.opacity = Math.round(props.object.opacity * 100)
|
||||
data.radius = Math.round(props.object.rx)
|
||||
data.fill = props.object.fill
|
||||
// data.stroke = props.object.stroke
|
||||
// data.strokeWidth = props.object.strokeWidth
|
||||
data.stroke = props.object.stroke
|
||||
data.strokeWidth = props.object.strokeWidth
|
||||
}
|
||||
updateData()
|
||||
watch(() => props.object, updateData)
|
||||
@@ -167,14 +102,16 @@
|
||||
const setPriority = (isRecord: boolean) => {
|
||||
const options = {
|
||||
...data,
|
||||
opacity: data.opacity / 100,
|
||||
scaleX: data.scaleX / 100,
|
||||
scaleY: data.scaleY / 100
|
||||
opacity: data.opacity / 100
|
||||
}
|
||||
if (props.object.type === 'rect') {
|
||||
options['rx'] = data.radius
|
||||
options['ry'] = data.radius
|
||||
}
|
||||
if (props.object.type !== 'path') {
|
||||
delete options.stroke
|
||||
delete options.strokeWidth
|
||||
}
|
||||
delete options.radius
|
||||
objectManager.updateProperty(id.value, options, isRecord)
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
width: 0;
|
||||
top: 2.2rem;
|
||||
left: 3rem;
|
||||
max-height: 80%;
|
||||
max-height: 90%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.6rem;
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<span class="icon"><svg-icon name="dc-layer" size="16" /></span>
|
||||
<span class="title">Layer</span>
|
||||
<span class="title">{{ $t('DepthCanvas.layer') }}</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span class="icon" @click="addLayer"><svg-icon name="add" size="14" /></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="content mini-scrollbar">
|
||||
<VueDraggable
|
||||
:model-value="list"
|
||||
@start="(e) => handleDragStart(e)"
|
||||
@@ -173,13 +173,6 @@
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 20rem;
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortable-layers-child {
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<div class="depth-input" :class="{ color: isColor }">
|
||||
<span class="decorate"></span>
|
||||
<span v-show="icon" class="icon">
|
||||
<svg-icon :name="icon" :size="iconSize" size-unit="px" />
|
||||
<svg-icon :name="icon" :size="iconSize" />
|
||||
</span>
|
||||
<span v-show="before" class="before">{{ before }}</span>
|
||||
|
||||
<input
|
||||
v-if="!isColor"
|
||||
v-bind="attrs"
|
||||
:value="modelValue"
|
||||
@input="onInput"
|
||||
@@ -13,15 +15,17 @@
|
||||
@copy.stop
|
||||
@keydown.stop
|
||||
/>
|
||||
<input
|
||||
v-if="isColor"
|
||||
readonly
|
||||
type="text"
|
||||
:value="colorObj.color"
|
||||
@copy.stop
|
||||
@keydown.stop
|
||||
/>
|
||||
<template v-if="isColor">
|
||||
<template v-else>
|
||||
<input
|
||||
v-bind="attrs"
|
||||
type="color"
|
||||
:value="colorObj.color"
|
||||
@input="onInput"
|
||||
@change="onChange"
|
||||
@copy.stop
|
||||
@keydown.stop
|
||||
/>
|
||||
<input readonly :value="colorObj.color" @copy.stop @keydown.stop />
|
||||
<span class="decorate marginl"></span>
|
||||
<input
|
||||
class="alpha"
|
||||
@@ -35,6 +39,7 @@
|
||||
@keydown.stop
|
||||
/>
|
||||
</template>
|
||||
|
||||
<span v-show="after" class="after">{{ after }}</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -220,45 +225,45 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
border: 1px solid var(--depth-input-border-color, rgba(230, 230, 231, 1));
|
||||
border-radius: 2px;
|
||||
height: var(--depth-input-height, 20px);
|
||||
border: 0.1rem solid var(--depth-input-border-color, rgba(230, 230, 231, 1));
|
||||
border-radius: 0.2rem;
|
||||
height: var(--depth-input-height, 2rem);
|
||||
background-color: var(--depth-input-bg-color, #fff);
|
||||
padding: 0 4px 0 2px;
|
||||
padding: 0 0.4rem 0 0.2rem;
|
||||
&.color {
|
||||
--depth-input-decorate-margin-right: 10px;
|
||||
--depth-input-input-margin-right: 10px;
|
||||
--depth-input-decorate-margin-right: 1rem;
|
||||
--depth-input-input-margin-right: 1rem;
|
||||
--depth-input-input-font-align: left;
|
||||
--depth-input-after-color: rgba(181, 181, 181, 1);
|
||||
}
|
||||
> .decorate {
|
||||
width: 2px;
|
||||
width: 0.2rem;
|
||||
background-color: var(--depth-input-decorate-color, rgba(230, 230, 231, 1));
|
||||
border-radius: 2px;
|
||||
border-radius: 0.2rem;
|
||||
height: 75%;
|
||||
margin-right: var(--depth-input-decorate-margin-right, 4px);
|
||||
margin-right: var(--depth-input-decorate-margin-right, 0.4rem);
|
||||
}
|
||||
> .iconfont {
|
||||
font-size: 12px;
|
||||
font-size: 1.2rem;
|
||||
color: #000;
|
||||
margin-right: 4px;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
> .before {
|
||||
font-size: 12px;
|
||||
font-size: 1.2rem;
|
||||
color: #000;
|
||||
margin-right: 4px;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
> .after {
|
||||
font-size: 12px;
|
||||
font-size: 1.2rem;
|
||||
color: var(--depth-input-after-color, #000);
|
||||
margin-left: 2px;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
> input {
|
||||
font-size: 12px;
|
||||
font-size: 1.2rem;
|
||||
width: 0;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
margin-right: var(--depth-input-input-margin-right, 0);
|
||||
margin-right: var(--depth-input-input-margin-right, 0rem);
|
||||
text-align: var(--depth-input-input-font-align, right);
|
||||
outline: none;
|
||||
border: none;
|
||||
@@ -277,9 +282,9 @@
|
||||
}
|
||||
&[type='color'] {
|
||||
flex: 0.5;
|
||||
border-radius: 2px;
|
||||
border-radius: 0.2rem;
|
||||
height: 70%;
|
||||
border: 1px solid rgb(42, 42, 42);
|
||||
border: 0.1rem solid rgb(42, 42, 42);
|
||||
display: block;
|
||||
&::-webkit-color-swatch-wrapper {
|
||||
padding: 0;
|
||||
|
||||
@@ -152,52 +152,52 @@
|
||||
justify-content: center;
|
||||
> * {
|
||||
flex: 1;
|
||||
margin-right: 10px;
|
||||
margin-right: 1rem;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .dish {
|
||||
width: 115px;
|
||||
height: 115px;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 3.4px;
|
||||
width: 11.5rem;
|
||||
height: 11.5rem;
|
||||
border: 0.1rem solid #eaeaea;
|
||||
border-radius: 0.4rem;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
background-color: #f6f6f6;
|
||||
margin-top: 20px;
|
||||
margin-top: 2rem;
|
||||
> * {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
> img {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
bottom: 3.5px;
|
||||
right: 3.5px;
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
bottom: 0.35rem;
|
||||
right: 0.35rem;
|
||||
}
|
||||
> .ball {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 8.5px;
|
||||
height: 8.5px;
|
||||
border: 1px solid #fff;
|
||||
width: 0.85rem;
|
||||
height: 0.85rem;
|
||||
border: 0.1rem solid #fff;
|
||||
background-color: #333;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 0.68px 0.17px 0px rgba(0, 0, 0, 0.26);
|
||||
box-shadow: 9 0.07rem 0.02rem 0 rgba(0, 0, 0, 0.26);
|
||||
}
|
||||
> .tip {
|
||||
font-size: 8.5px;
|
||||
font-size: 0.85rem;
|
||||
color: #000;
|
||||
line-height: 24px;
|
||||
line-height: 2.4rem;
|
||||
&.x {
|
||||
top: 50%;
|
||||
right: 0%;
|
||||
transform: translate(100%, -50%);
|
||||
padding-left: 6px;
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
&.y {
|
||||
top: 0%;
|
||||
@@ -216,15 +216,15 @@
|
||||
transform: translate(-50%, -50%);
|
||||
&.x {
|
||||
width: 100%;
|
||||
border-top-width: 1px;
|
||||
border-top-width: 0.1rem;
|
||||
}
|
||||
&.y {
|
||||
height: 100%;
|
||||
border-left-width: 1px;
|
||||
border-left-width: 0.1rem;
|
||||
}
|
||||
&.z {
|
||||
width: 50%;
|
||||
border-top-width: 1px;
|
||||
border-top-width: 0.1rem;
|
||||
border-color: #454754;
|
||||
transform: translate(0%, -50%) rotateZ(var(--rotateZ));
|
||||
transform-origin: left center;
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
<style scoped lang="less">
|
||||
.depth-select {
|
||||
&:deep(.el-select) {
|
||||
--el-select-input-font-size: 12px;
|
||||
--el-select-input-font-size: 1.2rem;
|
||||
.el-select__wrapper {
|
||||
font-size: 12px;
|
||||
font-size: 1.2rem;
|
||||
min-height: 0;
|
||||
height: 28px;
|
||||
padding: 0 8px;
|
||||
height: 2.8rem;
|
||||
padding: 0 0.8rem;
|
||||
}
|
||||
.el-select__selected-item,
|
||||
.el-select__input-wrapper,
|
||||
@@ -35,17 +35,17 @@
|
||||
line-height: normal;
|
||||
}
|
||||
.el-select__input {
|
||||
height: 24px;
|
||||
height: 2.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.el-popper {
|
||||
.el-select-dropdown {
|
||||
li {
|
||||
padding-left: 8px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-size: 12px;
|
||||
padding-left: 0.8rem;
|
||||
height: 3rem;
|
||||
line-height: 3rem;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
--input-thumb-size: 1.4rem;
|
||||
--input-thumb-size: 1.2rem;
|
||||
--backcolor1: var(--depth-slider-thumb-color1, #000);
|
||||
--backcolor2: var(--depth-slider-thumb-color2, #d3d3d3);
|
||||
&:hover {
|
||||
@@ -121,7 +121,7 @@
|
||||
background: var(--backcolor1); /* 蓝色滑块 */
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0.1rem 0.2rem rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
&::-webkit-slider-thumb:hover {
|
||||
transform: scale(1.1);
|
||||
@@ -153,9 +153,9 @@
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 5px solid transparent;
|
||||
border-right: 5px solid transparent;
|
||||
border-top: 5px solid rgba(0, 0, 0, 0.8);
|
||||
border-left: 0.5rem solid transparent;
|
||||
border-right: 0.5rem solid transparent;
|
||||
border-top: 0.5rem solid rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +105,7 @@
|
||||
const json = canvasManager.processCanvasDisUrlJSON(canvasData)
|
||||
await canvasManager.loadJSON(json)
|
||||
}
|
||||
layerManager.setActiveFirstLayer()
|
||||
globalStore.setLoading(false)
|
||||
|
||||
stateManager.onMounted()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { fabric } from 'fabric-with-all'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
import { getObjectAlphaToCanvas, traceImageContour, cloneObjects } from '../tools/canvasMethod'
|
||||
import { getSegAnythingImage } from '@/api/depth-canvas'
|
||||
import { useGlobalStore, useUserInfoStore } from '@/stores'
|
||||
|
||||
/** 智能框选工具管理器 */
|
||||
export class AISelectboxToolManager {
|
||||
@@ -10,6 +12,7 @@ export class AISelectboxToolManager {
|
||||
layerManager: any
|
||||
toolManager: any
|
||||
|
||||
targetObject: any
|
||||
isDragging: boolean = false
|
||||
startX: number = 0
|
||||
startY: number = 0
|
||||
@@ -37,6 +40,7 @@ export class AISelectboxToolManager {
|
||||
if (!oldIsAAA && newIsAAA) {
|
||||
// 普通工具切换到智能框选工具
|
||||
this.init()
|
||||
this.canvasManager.discardActiveObject()
|
||||
} else if (oldIsAAA && !newIsAAA) {
|
||||
// 智能框选工具切换到普通工具
|
||||
this.clear()
|
||||
@@ -53,6 +57,19 @@ export class AISelectboxToolManager {
|
||||
this.clear();
|
||||
this.createDemoObject()
|
||||
this.tcanvas = null;
|
||||
this.targetObject = null;
|
||||
this.canvasManager.getObjects().forEach((obj) => {
|
||||
if (obj?.info?.isOriginal) {
|
||||
this.targetObject = obj.toObject();
|
||||
}
|
||||
})
|
||||
// const image = this.layerManager.getActiveLayer()
|
||||
// if (image && image.info.isAiSelect) {
|
||||
// this.targetObject = image;
|
||||
// } else {
|
||||
// this.targetObject = null;
|
||||
// }
|
||||
|
||||
}
|
||||
clear() {
|
||||
console.log("清除智能框选工具")
|
||||
@@ -101,6 +118,10 @@ export class AISelectboxToolManager {
|
||||
this.canvasManager.canvas.remove(this.indicatorObject)
|
||||
this.indicatorObject = null
|
||||
}
|
||||
resetDemoObject() {
|
||||
this.clearDemoObject()
|
||||
this.createDemoObject()
|
||||
}
|
||||
// 创建临时画布对象
|
||||
async createStaticCanvas(object: fabric.Object) {
|
||||
if (!this.demoObject) this.createDemoObject()
|
||||
@@ -116,6 +137,7 @@ export class AISelectboxToolManager {
|
||||
}
|
||||
// 处理绘制图像
|
||||
async handleDrawImage(object: fabric.Object) {
|
||||
if (!this.targetObject) return;
|
||||
const tcanvas = await this.createStaticCanvas(this.demoObject)
|
||||
tcanvas.add(object)
|
||||
tcanvas.renderAll();
|
||||
@@ -124,6 +146,7 @@ export class AISelectboxToolManager {
|
||||
}
|
||||
// 处理图像删除
|
||||
async handleRemoveImage(object: fabric.Object) {
|
||||
if (!this.targetObject) return;
|
||||
const tcanvas = await this.createStaticCanvas(this.demoObject)
|
||||
const rcanvas = await this.createStaticCanvas(object)
|
||||
const tempCanvas = rcanvas.toCanvasElement();
|
||||
@@ -142,6 +165,7 @@ export class AISelectboxToolManager {
|
||||
}
|
||||
|
||||
mouseDownEvent(e) {
|
||||
if (!this.targetObject) return;
|
||||
const tool = this.toolManager.currentTool.value
|
||||
const tools = [OperationType.AISELECT_ADD, OperationType.AISELECT_REMOVE]
|
||||
if (!tools.includes(tool)) return;
|
||||
@@ -152,6 +176,7 @@ export class AISelectboxToolManager {
|
||||
this.createIndicatorObject()
|
||||
}
|
||||
mouseMoveEvent(e) {
|
||||
if (!this.targetObject) return;
|
||||
if (!this.isDragging) return;
|
||||
var width = e.absolutePointer.x - this.startX
|
||||
var height = e.absolutePointer.y - this.startY
|
||||
@@ -168,7 +193,8 @@ export class AISelectboxToolManager {
|
||||
this.indicatorObject.set({ width, height, left, top })
|
||||
this.canvasManager.canvas.renderAll()
|
||||
}
|
||||
mouseUpEvent(e) {
|
||||
async mouseUpEvent(e) {
|
||||
if (!this.targetObject) return;
|
||||
if (!this.isDragging) return;
|
||||
this.isDragging = false;
|
||||
const object = this.indicatorObject.toJSON("evented")
|
||||
@@ -177,20 +203,32 @@ export class AISelectboxToolManager {
|
||||
this.clearIndicatorObject()
|
||||
this.canvasManager.canvas.renderAll()
|
||||
|
||||
const rect = new fabric.Rect({
|
||||
left: object.left,
|
||||
top: object.top,
|
||||
width: object.width,
|
||||
height: object.height,
|
||||
fill: '#f00',
|
||||
strokeWidth: 0,
|
||||
})
|
||||
useGlobalStore().setLoading(true)
|
||||
const x1 = Math.round(object.left)
|
||||
const y1 = Math.round(object.top)
|
||||
const x2 = Math.round(object.left + object.width)
|
||||
const y2 = Math.round(object.top + object.height)
|
||||
const data = {
|
||||
type: "box",
|
||||
box: [x1, y1, x2, y2],
|
||||
}
|
||||
const url = await this.getSegAnythingImage(data)
|
||||
const image = await this.loadImageToObject(url)
|
||||
// const rect = new fabric.Rect({
|
||||
// left: object.left,
|
||||
// top: object.top,
|
||||
// width: object.width,
|
||||
// height: object.height,
|
||||
// fill: '#f00',
|
||||
// strokeWidth: 0,
|
||||
// })
|
||||
const currentTool = this.toolManager.currentTool.value
|
||||
if (currentTool === OperationType.AISELECT_ADD) {
|
||||
this.handleDrawImage(rect)
|
||||
await this.handleDrawImage(image)
|
||||
} else if (currentTool === OperationType.AISELECT_REMOVE) {
|
||||
this.handleRemoveImage(rect)
|
||||
await this.handleRemoveImage(image)
|
||||
}
|
||||
useGlobalStore().setLoading(false)
|
||||
}
|
||||
|
||||
|
||||
@@ -213,15 +251,17 @@ export class AISelectboxToolManager {
|
||||
absolutePositioned: true,
|
||||
};
|
||||
async createSelectbox() {
|
||||
if (!this.targetObject) return;
|
||||
if (!this.demoObject) return
|
||||
const fobject = this.demoObject
|
||||
this.clearDemoObject()
|
||||
const canvas = getObjectAlphaToCanvas(fobject, null, 0, { r: 255, g: 0, b: 0, a: 255 });
|
||||
const tcanvas = await this.createStaticCanvas(fobject)
|
||||
const canvas = getObjectAlphaToCanvas(tcanvas, null, 0, { r: 255, g: 0, b: 0, a: 255 });
|
||||
const arr = traceImageContour(canvas);
|
||||
const scaleY = fobject.scaleY
|
||||
const scaleX = fobject.scaleX
|
||||
const top = fobject.top
|
||||
const left = fobject.left
|
||||
const arr = traceImageContour(canvas);
|
||||
let minX = fobject.width;
|
||||
let minY = fobject.height;
|
||||
const str = arr.map((v) => {
|
||||
@@ -257,7 +297,26 @@ export class AISelectboxToolManager {
|
||||
this.toolManager.setTool(OperationType.SELECT)
|
||||
}
|
||||
|
||||
|
||||
/** 获取分隔后图片 */
|
||||
async getSegAnythingImage(obj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.targetObject) return;
|
||||
const user_id = useUserInfoStore().state.userInfo.id;
|
||||
const image_path = this.targetObject.src;
|
||||
const data = {
|
||||
image_path,
|
||||
user_id,
|
||||
...obj,
|
||||
}
|
||||
getSegAnythingImage(data).then((res) => {
|
||||
console.log(res)
|
||||
resolve(res)
|
||||
}).catch((error) => {
|
||||
console.error(error);
|
||||
useGlobalStore().setLoading(false)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.clear()
|
||||
|
||||
@@ -92,6 +92,7 @@ export class CanvasManager {
|
||||
id: createId("image"),
|
||||
name: "图片图层",
|
||||
lock: true,
|
||||
isOriginal: true,
|
||||
}
|
||||
})
|
||||
image = img
|
||||
|
||||
@@ -3,7 +3,9 @@ import { fabric } from 'fabric-with-all'
|
||||
import { createId } from '../../tools/tools'
|
||||
import { exportObjectsToImage, exportObjectToThumbnail } from '../tools/exportMethod'
|
||||
import { OperationType, BlendMode } from '../tools/layerHelper'
|
||||
import { getArrowPath, cloneObjects, getStarArr } from '../tools/canvasMethod'
|
||||
import { getArrowPath, getLinePath, cloneObjects, getStarArr } from '../tools/canvasMethod'
|
||||
import i18n from '@/lang/index'
|
||||
const t = i18n.global.t
|
||||
|
||||
export class LayerManager {
|
||||
stateManager: any
|
||||
@@ -29,6 +31,10 @@ export class LayerManager {
|
||||
this.stateManager.toolManager.setTool(OperationType.SELECT)
|
||||
}
|
||||
}
|
||||
setActiveFirstLayer() {
|
||||
this.setActiveID(this.layers.value[0]?.info?.id || "")
|
||||
}
|
||||
|
||||
getActiveLayer() {
|
||||
return this.getLayerById(this.activeID.value)
|
||||
}
|
||||
@@ -134,10 +140,11 @@ export class LayerManager {
|
||||
copyLayerById(id) {
|
||||
const object = this.canvasManager.getObjectById(id)
|
||||
if (!object) return console.warn('复制图层失败,对象不存在ID:', id)
|
||||
this.canvasManager.discardActiveObject()
|
||||
cloneObjects([object]).then(objects => {
|
||||
const newObject = objects[0]
|
||||
const info = JSON.parse(JSON.stringify(newObject.info))
|
||||
info.id = createId("image")
|
||||
info.id = createId("copylayer")
|
||||
// info.name = info.name
|
||||
newObject.set({
|
||||
top: newObject.top + 15,
|
||||
@@ -206,7 +213,7 @@ export class LayerManager {
|
||||
fill: 'transparent',
|
||||
info: {
|
||||
id: createId("image"),
|
||||
name: '空图层',
|
||||
name: t('DepthCanvas.emptyLayer'),
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(emptyObject)
|
||||
@@ -226,7 +233,7 @@ export class LayerManager {
|
||||
evented: false,
|
||||
info: {
|
||||
id: createId("group"),
|
||||
name: '智能选区组',
|
||||
name: t('DepthCanvas.aiGroupLayer'),
|
||||
showChildren: true,
|
||||
...(options?.info || {}),
|
||||
}
|
||||
@@ -245,7 +252,7 @@ export class LayerManager {
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("text"),
|
||||
name: '文本图层',
|
||||
name: t('DepthCanvas.textLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
@@ -263,7 +270,7 @@ export class LayerManager {
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("rect"),
|
||||
name: '矩形图层',
|
||||
name: t('DepthCanvas.rectLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
@@ -274,25 +281,27 @@ export class LayerManager {
|
||||
}
|
||||
/** 创建直线图层 */
|
||||
async createLineLayer(options?: any, isRecord = true, isActive = true) {
|
||||
const line = [options?.x1 || 0, options?.y1 || 0, options?.x2 || 100, options?.y2 || 0]
|
||||
delete options.x1
|
||||
delete options.y1
|
||||
delete options.x2
|
||||
delete options.y2
|
||||
const lineObject = new fabric.Line(line, {
|
||||
stroke: 'black', // 线条颜色
|
||||
strokeWidth: 2, // 线条粗细
|
||||
const width = options?.width || 100
|
||||
const height = options?.height || 2
|
||||
delete options.width
|
||||
delete options.height
|
||||
const arrowObject = new fabric.Path(getLinePath(width, height), {
|
||||
stroke: '#000', // 只设置边框颜色
|
||||
strokeWidth: 2, // 边框宽度
|
||||
fill: 'transparent', // 不填充
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("line"),
|
||||
name: '直线图层',
|
||||
name: t('DepthCanvas.lineLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
this.setLayerPosition(lineObject, options)
|
||||
await this.canvasManager.add(lineObject, isRecord)
|
||||
if (isActive) this.setActiveID(lineObject.info.id)
|
||||
return lineObject
|
||||
});
|
||||
this.setLayerPosition(arrowObject, options)
|
||||
await this.canvasManager.add(arrowObject, isRecord)
|
||||
if (isActive) this.setActiveID(arrowObject.info.id)
|
||||
return arrowObject
|
||||
}
|
||||
/** 创建椭圆图层 */
|
||||
async createEllipseLayer(options?: any, isRecord = true, isActive = true) {
|
||||
@@ -302,7 +311,7 @@ export class LayerManager {
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("ellipse"),
|
||||
name: '椭圆图层',
|
||||
name: t('DepthCanvas.ellipseLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
@@ -321,7 +330,7 @@ export class LayerManager {
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("triangle"),
|
||||
name: '三角形图层',
|
||||
name: t('DepthCanvas.triangleLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
@@ -341,7 +350,7 @@ export class LayerManager {
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("star"),
|
||||
name: '五角星图层',
|
||||
name: t('DepthCanvas.starLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
@@ -353,19 +362,19 @@ export class LayerManager {
|
||||
/** 创建箭头图层 */
|
||||
async createArrowLayer(options?: any, isRecord = true, isActive = true) {
|
||||
const width = options?.width || 100
|
||||
const height = options?.height || 10
|
||||
const strokeWidth = options?.strokeWidth || 4
|
||||
delete options.width
|
||||
delete options.height
|
||||
const arrowObject = new fabric.Path(getArrowPath(width, height), {
|
||||
delete options.strokeWidth
|
||||
const arrowObject = new fabric.Path(getArrowPath(width, strokeWidth), {
|
||||
stroke: '#000', // 只设置边框颜色
|
||||
strokeWidth: 3, // 边框宽度
|
||||
strokeWidth, // 边框宽度
|
||||
fill: 'transparent', // 不填充
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("star"),
|
||||
name: '箭头图层',
|
||||
id: createId("arrow"),
|
||||
name: t('DepthCanvas.arrowLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
});
|
||||
@@ -395,7 +404,7 @@ export class LayerManager {
|
||||
...(options || {}),
|
||||
info: {
|
||||
id: createId("image"),
|
||||
name: "图片图层",
|
||||
name: t('DepthCanvas.imageLayer'),
|
||||
...(options?.info || {}),
|
||||
}
|
||||
})
|
||||
@@ -419,7 +428,7 @@ export class LayerManager {
|
||||
info: {
|
||||
...(targetLayer?.info || {}),
|
||||
id: createId("image"),
|
||||
name: targetLayer?.info?.name || "合并图层",
|
||||
name: targetLayer?.info?.name || t('DepthCanvas.mergeLayer'),
|
||||
}
|
||||
})
|
||||
resolve(img)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { OperationType, OperationTypes } from "../tools/layerHelper";
|
||||
import { getStarArr, getArrowPath, distance, angleBetweenPointsDegrees } from "../tools/canvasMethod";
|
||||
import { getStarArr, getArrowPath, getLinePath, distance, angleBetweenPointsDegrees } from "../tools/canvasMethod";
|
||||
import { fabric } from 'fabric-with-all'
|
||||
/** 形状管理器 */
|
||||
export class ShapeToolManager {
|
||||
@@ -136,22 +136,47 @@ export class ShapeToolManager {
|
||||
|
||||
/** 绘制直线 */
|
||||
downLine() {
|
||||
const line = new fabric.Line([this.startX, this.startY, this.startX, this.startY], {
|
||||
stroke: 'black', // 线条颜色
|
||||
strokeWidth: 2 // 线条粗细
|
||||
})
|
||||
return line
|
||||
// const line = new fabric.Line([this.startX, this.startY, this.startX, this.startY], {
|
||||
// stroke: 'black', // 线条颜色
|
||||
// strokeWidth: 2 // 线条粗细
|
||||
// })
|
||||
// return line
|
||||
return new fabric.Path();
|
||||
}
|
||||
moveLine({ x, y }) {
|
||||
this.demoObject.set({
|
||||
x1: this.startX,
|
||||
y1: this.startY,
|
||||
x2: x,
|
||||
y2: y,
|
||||
})
|
||||
// this.demoObject.set({
|
||||
// x1: this.startX,
|
||||
// y1: this.startY,
|
||||
// x2: x,
|
||||
// y2: y,
|
||||
// })
|
||||
const width = distance(this.startX, this.startY, x, y)
|
||||
const angle = angleBetweenPointsDegrees(this.startX, this.startY, x, y)
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
const arrow = new fabric.Path(getLinePath(width, 2), {
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
stroke: '#000', // 只设置边框颜色
|
||||
strokeWidth: 2, // 边框宽度
|
||||
fill: 'transparent', // 不填充
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
originY: 'center',
|
||||
angle: angle,
|
||||
});
|
||||
this.canvasManager.canvas.add(arrow)
|
||||
this.demoObject = arrow
|
||||
}
|
||||
upLine(object) {
|
||||
this.layerManager.createLineLayer(object)
|
||||
if (object.originY !== "center") {
|
||||
this.layerManager.createLineLayer({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
})
|
||||
} else {
|
||||
this.layerManager.createLineLayer(object)
|
||||
}
|
||||
// this.layerManager.createLineLayer(object)
|
||||
}
|
||||
|
||||
/** 绘制椭圆 */
|
||||
@@ -228,11 +253,11 @@ export class ShapeToolManager {
|
||||
const width = distance(this.startX, this.startY, x, y)
|
||||
const angle = angleBetweenPointsDegrees(this.startX, this.startY, x, y)
|
||||
this.canvasManager.canvas.remove(this.demoObject)
|
||||
const arrow = new fabric.Path(getArrowPath(width, 10), {
|
||||
const arrow = new fabric.Path(getArrowPath(width, 4), {
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
stroke: '#000', // 只设置边框颜色
|
||||
strokeWidth: 3, // 边框宽度
|
||||
strokeWidth: 4, // 边框宽度
|
||||
fill: 'transparent', // 不填充
|
||||
strokeLineCap: 'round',
|
||||
strokeLineJoin: 'round',
|
||||
@@ -247,7 +272,7 @@ export class ShapeToolManager {
|
||||
this.layerManager.createArrowLayer({
|
||||
left: this.startX,
|
||||
top: this.startY,
|
||||
}, true)
|
||||
})
|
||||
} else {
|
||||
this.layerManager.createArrowLayer(object)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ref, computed } from "vue";
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { OperationType } from '../tools/layerHelper'
|
||||
import i18n from '@/lang'
|
||||
const t = i18n.global.t
|
||||
|
||||
@@ -73,7 +73,6 @@ export class StateManager {
|
||||
/** 记录状态 */
|
||||
recordState() {
|
||||
if (this.running.value) return
|
||||
console.log("recordState")
|
||||
this.running.value = true
|
||||
if (this.historyIndex.value < this.historyList.value.length - 1) {
|
||||
this.historyList.value.splice(this.historyIndex.value + 1)
|
||||
@@ -96,6 +95,7 @@ export class StateManager {
|
||||
this.running.value = true
|
||||
this.historyIndex.value = index
|
||||
this.canvasManager.loadJSON(state.canvas, false).then(() => {
|
||||
this.toolManager.setTool(OperationType.SELECT)
|
||||
this.event.emit('canvas:undo', state)
|
||||
this.running.value = false
|
||||
})
|
||||
@@ -109,6 +109,7 @@ export class StateManager {
|
||||
this.running.value = true
|
||||
this.historyIndex.value = index
|
||||
this.canvasManager.loadJSON(state.canvas, false).then(() => {
|
||||
this.toolManager.setTool(OperationType.SELECT)
|
||||
this.event.emit('canvas:redo', state)
|
||||
this.running.value = false
|
||||
})
|
||||
|
||||
@@ -753,6 +753,23 @@ export class CanvasEventManager {
|
||||
this.canvas.on("object:removed", (e) => {
|
||||
// updateLayers(e);
|
||||
});
|
||||
this.canvas.on("erasing:start", (e) => {
|
||||
// console.log("erasing:start", e);
|
||||
});
|
||||
this.canvas.on("erasing:end", async (e) => {
|
||||
// console.log("erasing:end", e);
|
||||
const targets = e.targets;
|
||||
var isRecord = false;
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
const target = targets[i];
|
||||
const id = target?.info?.id;
|
||||
if (id) {
|
||||
isRecord = true;
|
||||
await this.layerManager.updateLayerThumbnailsById(id)
|
||||
}
|
||||
}
|
||||
if (isRecord) this.stateManager.recordState();
|
||||
});
|
||||
}
|
||||
setupDoubleClickEvents() {
|
||||
// 双击处理
|
||||
|
||||
@@ -61,13 +61,26 @@ export function getStarArr(width = 0, height = 0) {
|
||||
}))
|
||||
}
|
||||
/** 获取箭头路径 */
|
||||
export function getArrowPath(width = 0, height = 0) {
|
||||
export function getArrowPath(width = 0, strokeWidth = 4) {
|
||||
const height = strokeWidth * 4
|
||||
const arr = [
|
||||
["M", 0, height / 2],
|
||||
["L", width, height / 2],
|
||||
["M", width - 8, 0],
|
||||
["M", width - height, 0],
|
||||
["L", width, height / 2],
|
||||
["L", width - height, height],
|
||||
]
|
||||
var path = ""
|
||||
arr.forEach(item => {
|
||||
path += item.join(" ") + " "
|
||||
})
|
||||
return path
|
||||
}
|
||||
/** 获取直线路径 */
|
||||
export function getLinePath(width = 0, height = 0) {
|
||||
const arr = [
|
||||
["M", 0, height / 2],
|
||||
["L", width, height / 2],
|
||||
["L", width - 8, height],
|
||||
]
|
||||
var path = ""
|
||||
arr.forEach(item => {
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<div class="my-select">
|
||||
<el-select :model-value="modelValue" @change="onChange" v-bind="attrs">
|
||||
<el-option v-for="v in list" :key="v.value" :label="v.label" :value="v.value" />
|
||||
<el-option v-for="v in list" :key="v.value" :label="v.label" :value="v.value" >
|
||||
<slot name="option" :item="v">
|
||||
<!-- 默认内容 -->
|
||||
<span>{{ v.label }}</span>
|
||||
</slot>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
<template>
|
||||
<div class="my-textTools">
|
||||
<input class="color" type="color" ref="colorInput" @change="changeColor" />
|
||||
<input class="color" type="color" v-model="textStyle['--font-color']" ref="colorInput" @change="changeColor" />
|
||||
<div class="interval"></div>
|
||||
<div class="fontFamily">
|
||||
<my-select v-model="textStyle['--font-family']" @change="changeFontFamily" :list="fontFamilyList[locale]" />
|
||||
<my-select v-model="textStyle['--font-family']" @change="changeFontFamily" :list="fontFamilyList[locale]" >
|
||||
<template #option="{ item }">
|
||||
<span :style="{'font-family': item.value,}">{{ item.label }}</span>
|
||||
</template>
|
||||
</my-select>
|
||||
</div>
|
||||
<div class="interval"></div>
|
||||
<div class="size">
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
<threeModel :currentData="currentData" />
|
||||
</template>
|
||||
</baseModal>
|
||||
<!-- <Assistant /> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -69,6 +70,7 @@
|
||||
import { computed, ref, watch, onMounted, nextTick, provide, onBeforeUnmount } from 'vue'
|
||||
import { useLayout } from '@/utils/treeDiagram'
|
||||
import { NODE_TYPE, NODE_COMPONENT } from './tools/index.d'
|
||||
import Assistant from '@/components/Assistant/assistant.vue'
|
||||
// 组件
|
||||
import headerTools from './components/header-tools.vue'
|
||||
import zoom from '../components/zoom.vue'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<el-dialog
|
||||
class="base-modal"
|
||||
v-model="showDialog"
|
||||
destroy-on-close
|
||||
align-center
|
||||
:show-close="false"
|
||||
:width="modalWidth"
|
||||
|
||||
@@ -78,6 +78,24 @@ export const base64Tofile = (base64: string,name: string) => {
|
||||
const file = new File([blob], name, { type: mime })
|
||||
return file
|
||||
}
|
||||
/** 二进制转base64 */
|
||||
/**
|
||||
* File 对象转 Base64
|
||||
* @param {File} file - 文件对象
|
||||
* @returns {Promise<string>} Base64 字符串
|
||||
*/
|
||||
export const fileToBase64 = (file: File) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = () => {
|
||||
resolve(reader.result) // Base64 字符串
|
||||
}
|
||||
reader.onerror = (error) => {
|
||||
reject(error)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
//获取当前时间2026-03-20 11:38:29
|
||||
export const getCurrentTime = () => {
|
||||
const now = new Date()
|
||||
|
||||
738
src/components/clipDialog.vue
Normal file
738
src/components/clipDialog.vue
Normal file
@@ -0,0 +1,738 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
class="clipDialog"
|
||||
v-model="showPanel"
|
||||
align-center
|
||||
:show-close="false"
|
||||
width="75rem"
|
||||
height="69rem"
|
||||
style="border-radius: 2rem; padding: 4rem; --el-dialog-padding-primary: 1rem"
|
||||
>
|
||||
<template #header="{ close }">
|
||||
<div class="clip-header">
|
||||
<div class="title">{{ $t("clipDialog.title") }}</div>
|
||||
<span class="close" @click="close">
|
||||
<svg-icon name="close" size="12" color="#000" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="crop-image-modal">
|
||||
<div class="modal-content">
|
||||
<div class="clip">
|
||||
<!-- 图片剪切 -->
|
||||
<div class="image-clip" ref="el" v-if="data.url">
|
||||
<div
|
||||
class="box"
|
||||
ref="box"
|
||||
:style="{
|
||||
width: clipData.img_width + 'px',
|
||||
height: clipData.img_height + 'px',
|
||||
}"
|
||||
>
|
||||
<img :src="data.url" />
|
||||
<div class="shade"></div>
|
||||
<div
|
||||
ref="clipRef"
|
||||
class="clip"
|
||||
:style="{
|
||||
top: clipData.top + 'px',
|
||||
left: clipData.left + 'px',
|
||||
width: clipData.width + 'px',
|
||||
height: clipData.height + 'px',
|
||||
}"
|
||||
@mousedown.stop="clipMousedown"
|
||||
@touchstart.stop="clipMousedown"
|
||||
>
|
||||
<div class="img">
|
||||
<img
|
||||
:src="data.url"
|
||||
:style="{
|
||||
width: clipData.img_width + 'px',
|
||||
height: clipData.img_height + 'px',
|
||||
top: -clipData.top + 'px',
|
||||
left: -clipData.left + 'px',
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="top"
|
||||
@mousedown.stop="topMousedown"
|
||||
@touchstart.stop="topMousedown"
|
||||
></div>
|
||||
<div
|
||||
class="right"
|
||||
@mousedown.stop="rightMousedown"
|
||||
@touchstart.stop="rightMousedown"
|
||||
></div>
|
||||
<div
|
||||
class="bottom"
|
||||
@mousedown.stop="bottomMousedown"
|
||||
@touchstart.stop="bottomMousedown"
|
||||
></div>
|
||||
<div
|
||||
class="left"
|
||||
@mousedown.stop="leftMousedown"
|
||||
@touchstart.stop="leftMousedown"
|
||||
></div>
|
||||
<span
|
||||
class="top"
|
||||
@mousedown.stop="topMousedown"
|
||||
@touchstart.stop="topMousedown"
|
||||
></span>
|
||||
<span
|
||||
class="right"
|
||||
@mousedown.stop="rightMousedown"
|
||||
@touchstart.stop="rightMousedown"
|
||||
></span>
|
||||
<span
|
||||
class="bottom"
|
||||
@mousedown.stop="bottomMousedown"
|
||||
@touchstart.stop="bottomMousedown"
|
||||
></span>
|
||||
<span
|
||||
class="left"
|
||||
@mousedown.stop="leftMousedown"
|
||||
@touchstart.stop="leftMousedown"
|
||||
></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 空状态 -->
|
||||
<!-- <div class="empty-state" v-else>
|
||||
<div class="empty-icon">📷</div>
|
||||
<p>空</p>
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="operation">
|
||||
<div class="operation-btn">
|
||||
<svg-icon name="clipMinus" size="22" color="#000" />
|
||||
</div>
|
||||
<div class="slider">
|
||||
<el-slider v-model="clipSize" />
|
||||
</div>
|
||||
<div class="operation-btn">
|
||||
<svg-icon name="clipAdd" size="22" color="#000" />
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="modal-footer">
|
||||
<div class="image-count" @click="close">
|
||||
{{ $t("clipDialog.cancel") }}
|
||||
</div>
|
||||
<div class="image-submit" @click="confirm">
|
||||
{{ $t("clipDialog.confirm") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onDeactivated,
|
||||
reactive,
|
||||
nextTick,
|
||||
} from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
// Props
|
||||
const props = defineProps(
|
||||
{
|
||||
isRatio: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
}
|
||||
}
|
||||
);
|
||||
const { t } = useI18n();
|
||||
const clipSize = ref(0);
|
||||
var resolveFn: (value: string | PromiseLike<string>) => void;
|
||||
const showPanel = ref(false);
|
||||
const open = (url: string) => {
|
||||
showPanel.value = true;
|
||||
setImgage(url);
|
||||
return new Promise((resolve) => (resolveFn = resolve));
|
||||
};
|
||||
const close = () => {
|
||||
showPanel.value = false;
|
||||
};
|
||||
//提交选中的T图片
|
||||
const confirm = () => {
|
||||
const base64 = getImageBase64();
|
||||
resolveFn && resolveFn(base64);
|
||||
close();
|
||||
};
|
||||
|
||||
const data = reactive({
|
||||
url: "",
|
||||
});
|
||||
const el = ref();
|
||||
const box = ref();
|
||||
const clipRef = ref();
|
||||
const clipData = reactive({
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
img_width: 0,
|
||||
img_height: 0,
|
||||
});
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
const reload = () => {
|
||||
if (!el.value) return;
|
||||
const size = Math.min(
|
||||
1,
|
||||
el.value.offsetWidth / canvas.width,
|
||||
el.value.offsetHeight / canvas.height
|
||||
);
|
||||
const width = size * canvas.width;
|
||||
const height = size * canvas.height;
|
||||
clipSize.value = Math.min(width, height);
|
||||
clipData.left = 0;
|
||||
clipData.top = 0;
|
||||
clipData.width = props.isRatio ? clipSize.value : width;
|
||||
clipData.height = props.isRatio ? clipSize.value : height;
|
||||
clipData.img_width = width;
|
||||
clipData.img_height = height;
|
||||
};
|
||||
window.addEventListener("resize", reload);
|
||||
onDeactivated(() => {
|
||||
window.removeEventListener("resize", reload);
|
||||
});
|
||||
|
||||
const setImgage = (url: string) => {
|
||||
if (!url) return;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const width = img.width;
|
||||
const height = img.height;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
ctx?.clearRect(0, 0, width, height);
|
||||
ctx?.drawImage(img, 0, 0, width, height);
|
||||
nextTick(() => reload());
|
||||
};
|
||||
img.src = url;
|
||||
data.url = url;
|
||||
};
|
||||
const canvasImageDataToBase64 = (imageData: any) => {
|
||||
// 创建一个临时的canvas元素
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = imageData.width;
|
||||
canvas.height = imageData.height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx?.putImageData(imageData, 0, 0);
|
||||
const dataURL = canvas.toDataURL("image/png");
|
||||
return dataURL;
|
||||
};
|
||||
const getImageBase64 = () => {
|
||||
const scale = canvas.width / clipData.img_width;
|
||||
const imageData = ctx?.getImageData(
|
||||
clipData.left * scale,
|
||||
clipData.top * scale,
|
||||
clipData.width * scale,
|
||||
clipData.height * scale
|
||||
);
|
||||
const base64 = canvasImageDataToBase64(imageData);
|
||||
return base64;
|
||||
};
|
||||
|
||||
// 移动裁剪框
|
||||
const clipMousedown = (e: MouseEvent | TouchEvent) => {
|
||||
e.preventDefault();
|
||||
var pageX = 0;
|
||||
var pageY = 0;
|
||||
if (e.type == "touchstart") {
|
||||
const touch = e["touches"][0];
|
||||
pageX = touch.pageX;
|
||||
pageY = touch.pageY;
|
||||
} else {
|
||||
pageX = e["pageX"];
|
||||
pageY = e["pageY"];
|
||||
}
|
||||
const elInfo = box.value.getBoundingClientRect();
|
||||
const clipInfo = clipRef.value.getBoundingClientRect();
|
||||
// 鼠标相对元素的位置
|
||||
const CX = pageX - clipInfo.x;
|
||||
const CY = pageY - clipInfo.y;
|
||||
|
||||
const mousemove = (e: MouseEvent | TouchEvent) => {
|
||||
e.preventDefault();
|
||||
var pageX = 0;
|
||||
var pageY = 0;
|
||||
if (e.type == "touchmove") {
|
||||
const touch = e["touches"][0];
|
||||
pageX = touch.pageX;
|
||||
pageY = touch.pageY;
|
||||
} else {
|
||||
pageX = e["pageX"];
|
||||
pageY = e["pageY"];
|
||||
}
|
||||
let x = pageX - elInfo.x - CX;
|
||||
let y = pageY - elInfo.y - CY;
|
||||
if (x < 0) {
|
||||
x = 0;
|
||||
} else if (x > elInfo.width - clipInfo.width) {
|
||||
x = elInfo.width - clipInfo.width;
|
||||
}
|
||||
if (y < 0) {
|
||||
y = 0;
|
||||
} else if (y > elInfo.height - clipInfo.height) {
|
||||
y = elInfo.height - clipInfo.height;
|
||||
}
|
||||
|
||||
clipData.top = y;
|
||||
clipData.left = x;
|
||||
clipData.img_width = elInfo.width;
|
||||
clipData.img_height = elInfo.height;
|
||||
};
|
||||
const mouseup = () => {
|
||||
window.removeEventListener("mousemove", mousemove);
|
||||
window.removeEventListener("mouseup", mouseup);
|
||||
window.removeEventListener("touchmove", mousemove);
|
||||
window.removeEventListener("touchend", mouseup);
|
||||
};
|
||||
if (e.type == "touchstart") {
|
||||
window.addEventListener("touchmove", mousemove);
|
||||
window.addEventListener("touchend", mouseup);
|
||||
} else {
|
||||
window.addEventListener("mousemove", mousemove);
|
||||
window.addEventListener("mouseup", mouseup);
|
||||
}
|
||||
};
|
||||
|
||||
// 移动裁剪框的四个边框
|
||||
const mousedown = (
|
||||
e: MouseEvent | TouchEvent,
|
||||
type: "top" | "bottom" | "right" | "left"
|
||||
) => {
|
||||
const minWidth = 20;
|
||||
const minHeight = 20;
|
||||
const elInfo = box.value.getBoundingClientRect();
|
||||
const R = elInfo.width - clipData.left - clipData.width;
|
||||
const B = elInfo.height - clipData.top - clipData.height;
|
||||
const noRatioMousemove = (e: MouseEvent | TouchEven)=>{
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
if (e.type == "touchmove") {
|
||||
const touch = e["touches"][0];
|
||||
x = touch.pageX - elInfo.x;
|
||||
y = touch.pageY - elInfo.y;
|
||||
} else {
|
||||
x = e["pageX"] - elInfo.x;
|
||||
y = e["pageY"] - elInfo.y;
|
||||
}
|
||||
|
||||
if (type == "right") {
|
||||
let width = x - clipData.left;
|
||||
if (width + clipData.left > elInfo.width) {
|
||||
width = elInfo.width - clipData.left;
|
||||
}
|
||||
if (width < minWidth) width = minWidth;
|
||||
clipData.width = width;
|
||||
} else if (type == "bottom") {
|
||||
let height = y - clipData.top;
|
||||
if (height + clipData.top > elInfo.height) {
|
||||
height = elInfo.height - clipData.top;
|
||||
}
|
||||
if (height < minHeight) height = minHeight;
|
||||
clipData.height = height;
|
||||
} else if (type == "left") {
|
||||
let left = x;
|
||||
let width = clipData.width;
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
} else if (left > elInfo.width - R - minWidth) {
|
||||
left = elInfo.width - R - minWidth;
|
||||
}
|
||||
width = elInfo.width - R - left;
|
||||
clipData.left = left;
|
||||
clipData.width = width;
|
||||
clipData.img_width = elInfo.width;
|
||||
clipData.img_height = elInfo.height;
|
||||
} else if (type == "top") {
|
||||
let top = y;
|
||||
let height = clipData.height;
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top > elInfo.height - B - minHeight) {
|
||||
top = elInfo.height - B - minHeight;
|
||||
}
|
||||
height = elInfo.height - B - top;
|
||||
clipData.top = top;
|
||||
clipData.height = height;
|
||||
clipData.img_width = elInfo.width;
|
||||
}
|
||||
}
|
||||
const ratioMousemove = (e: MouseEvent | TouchEvent) => {
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
if (e.type == "touchmove") {
|
||||
const touch = e["touches"][0];
|
||||
x = touch.pageX - elInfo.x;
|
||||
y = touch.pageY - elInfo.y;
|
||||
} else {
|
||||
x = e["pageX"] - elInfo.x;
|
||||
y = e["pageY"] - elInfo.y;
|
||||
}
|
||||
if (type == "right" || type == "bottom") {
|
||||
let width = x - clipData.left;
|
||||
let height = y - clipData.top;
|
||||
if (width + clipData.left > elInfo.width) {
|
||||
width = elInfo.width - clipData.left;
|
||||
}
|
||||
if (height + clipData.top > elInfo.height) {
|
||||
height = elInfo.height - clipData.top;
|
||||
}
|
||||
|
||||
if (type == "right") {
|
||||
if (width < minWidth) {
|
||||
width = minWidth;
|
||||
}
|
||||
height = width;
|
||||
if (height + clipData.top > elInfo.height) {
|
||||
height = elInfo.height - clipData.top;
|
||||
width = height;
|
||||
}
|
||||
} else if (type == "bottom") {
|
||||
if (height < minHeight) {
|
||||
height = minHeight;
|
||||
}
|
||||
width = height;
|
||||
if (width + clipData.left > elInfo.width) {
|
||||
width = elInfo.width - clipData.left;
|
||||
height = width;
|
||||
}
|
||||
}
|
||||
clipData.width = width;
|
||||
clipData.height = height;
|
||||
} else if (type == "left" || type == "top") {
|
||||
let left = x;
|
||||
let top = y;
|
||||
let width = clipData.width;
|
||||
let height = clipData.height;
|
||||
if (type == "left") {
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
} else if (left > elInfo.width - R - minWidth) {
|
||||
left = elInfo.width - R - minWidth;
|
||||
}
|
||||
width = elInfo.width - R - left;
|
||||
top = elInfo.height - B - width;
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
width = height;
|
||||
left = elInfo.width - R - width;
|
||||
}
|
||||
height = elInfo.height - B - top;
|
||||
} else if (type == "top") {
|
||||
if (top < 0) {
|
||||
top = 0;
|
||||
} else if (top > elInfo.height - B - minHeight) {
|
||||
top = elInfo.height - B - minHeight;
|
||||
}
|
||||
height = elInfo.height - B - top;
|
||||
left = elInfo.width - R - height;
|
||||
if (left < 0) {
|
||||
left = 0;
|
||||
height = width;
|
||||
top = elInfo.height - B - height;
|
||||
}
|
||||
width = elInfo.width - R - left;
|
||||
}
|
||||
clipData.top = top;
|
||||
clipData.left = left;
|
||||
clipData.width = width;
|
||||
clipData.height = height;
|
||||
clipData.img_width = elInfo.width;
|
||||
clipData.img_height = elInfo.height;
|
||||
}
|
||||
}
|
||||
const mousemove = (e: MouseEvent | TouchEvent) => {
|
||||
if(props.isRatio){
|
||||
ratioMousemove(e)
|
||||
}else{
|
||||
noRatioMousemove(e)
|
||||
}
|
||||
};
|
||||
const mouseup = () => {
|
||||
window.removeEventListener("mousemove", mousemove);
|
||||
window.removeEventListener("mouseup", mouseup);
|
||||
window.removeEventListener("touchmove", mousemove);
|
||||
window.removeEventListener("touchend", mouseup);
|
||||
};
|
||||
if (e.type == "touchstart") {
|
||||
window.addEventListener("touchmove", mousemove);
|
||||
window.addEventListener("touchend", mouseup);
|
||||
} else {
|
||||
window.addEventListener("mousemove", mousemove);
|
||||
window.addEventListener("mouseup", mouseup);
|
||||
}
|
||||
};
|
||||
const topMousedown = (e: MouseEvent | TouchEvent) => {
|
||||
mousedown(e, "top");
|
||||
};
|
||||
const rightMousedown = (e: MouseEvent | TouchEvent) => {
|
||||
mousedown(e, "right");
|
||||
};
|
||||
const bottomMousedown = (e: MouseEvent | TouchEvent) => {
|
||||
mousedown(e, "bottom");
|
||||
};
|
||||
const leftMousedown = (e: MouseEvent | TouchEvent) => {
|
||||
mousedown(e, "left");
|
||||
};
|
||||
defineExpose({
|
||||
open,
|
||||
close,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.clip-header {
|
||||
margin-top: 0.2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 3.3rem;
|
||||
border-bottom: 0.1rem solid rgba(0, 0, 0, 0.1);
|
||||
> .title {
|
||||
font-family: Semibold;
|
||||
font-size: 1.6rem;
|
||||
color: #000;
|
||||
}
|
||||
.close{
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
/* 弹窗主体 */
|
||||
.crop-image-modal {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
color: #666;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
opacity: 0.7;
|
||||
transition: all 0.2s;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: #333;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗内容 */
|
||||
.modal-content {
|
||||
// padding: 20px;
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
display: flex;
|
||||
width: 47.2rem;
|
||||
height: 35.20rem;
|
||||
margin: 4rem auto;
|
||||
> .clip{
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURf///9ra2sgNdccAAAARSURBVAjXY2A/wICMfjAgIwB8gwi84a8abQAAAABJRU5ErkJggg==);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
}
|
||||
.operation{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 52rem;
|
||||
margin: 0 auto;
|
||||
gap: 2rem;
|
||||
> .operation-btn{
|
||||
cursor: pointer;
|
||||
}
|
||||
> .slider{
|
||||
flex: 1;
|
||||
:deep(.el-slider){
|
||||
--el-slider-main-bg-color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
.image-clip {
|
||||
user-select: none;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> .box {
|
||||
position: relative;
|
||||
> img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
> .shade {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
> .clip {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
outline: 1px solid #39f;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
}
|
||||
> .img {
|
||||
pointer-events: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
> img {
|
||||
position: absolute;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKAQMAAAC3/F3+AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAGUExURf///9ra2sgNdccAAAARSURBVAjXY2A/wICMfjAgIwB8gwi84a8abQAAAABJRU5ErkJggg==);
|
||||
}
|
||||
}
|
||||
|
||||
> .top {
|
||||
cursor: n-resize;
|
||||
}
|
||||
> .bottom {
|
||||
cursor: s-resize;
|
||||
}
|
||||
> .right {
|
||||
cursor: e-resize;
|
||||
}
|
||||
> .left {
|
||||
cursor: w-resize;
|
||||
}
|
||||
|
||||
--border: 4px;
|
||||
--trbl: calc(0px - var(--border) / 2);
|
||||
--ball-size: 8px;
|
||||
--ball-trbl: calc(0px - var(--ball-size) / 2);
|
||||
> div.top {
|
||||
top: var(--trbl);
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--border);
|
||||
}
|
||||
|
||||
> div.bottom {
|
||||
bottom: var(--trbl);
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: var(--border);
|
||||
}
|
||||
> div.left {
|
||||
top: 0;
|
||||
left: var(--trbl);
|
||||
bottom: 0;
|
||||
width: var(--border);
|
||||
}
|
||||
> div.right {
|
||||
top: 0;
|
||||
right: var(--trbl);
|
||||
bottom: 0;
|
||||
width: var(--border);
|
||||
}
|
||||
> span {
|
||||
position: absolute;
|
||||
width: var(--ball-size);
|
||||
height: var(--ball-size);
|
||||
border-radius: 50%;
|
||||
background-color: #39f;
|
||||
}
|
||||
> span.top {
|
||||
top: var(--ball-trbl);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
> span.right {
|
||||
right: var(--ball-trbl);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
> span.bottom {
|
||||
bottom: var(--ball-trbl);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
> span.left {
|
||||
left: var(--ball-trbl);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #666;
|
||||
min-height: 300px;
|
||||
flex: 1;
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗底部 */
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
gap: 1.6rem;
|
||||
// margin-top: 6.6rem;
|
||||
> div {
|
||||
cursor: pointer;
|
||||
width: 7.7rem;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
line-height: 2.8rem;
|
||||
font-weight: 500;
|
||||
border-radius: 2rem;
|
||||
border: 1px solid #000;
|
||||
}
|
||||
.image-count{
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
.image-submit{
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.image-count {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -81,7 +81,8 @@ export default {
|
||||
userAgreement: 'User Agreement',
|
||||
privacyPolicy: 'Privacy Policy',
|
||||
view: 'View',
|
||||
remainingNum: 'Remaining number of times to upload profile picture:'
|
||||
remainingNum: 'Remaining number of times to upload profile picture:',
|
||||
notFound: 'Project not found',
|
||||
},
|
||||
Country: {
|
||||
unitedStates: 'United States',
|
||||
@@ -135,7 +136,9 @@ export default {
|
||||
confirm: 'Confirm',
|
||||
styleTitle: 'Settings',
|
||||
createProject: 'Create Project',
|
||||
trendingReport: 'Trending report'
|
||||
trendingReport: 'Trending report',
|
||||
reportPlaceholder:
|
||||
'Generate a furniture trending report for 2026, including popular styles and design directions.'
|
||||
},
|
||||
area: {
|
||||
unitedStates: 'United States',
|
||||
@@ -200,5 +203,54 @@ export default {
|
||||
threeModel: {
|
||||
loading: 'Loading',
|
||||
download: 'Download'
|
||||
},
|
||||
DepthCanvas: {
|
||||
layer: "Layer",
|
||||
editDetails: "Edit Details",
|
||||
export: "Export",
|
||||
save: "Save",
|
||||
workbench: "Workbench",
|
||||
position: "Position",
|
||||
size: "Size",
|
||||
appearance: "Appearance",
|
||||
opacity: "Opacity",
|
||||
cornerRadius: "Cor Radius",
|
||||
strokeWidth: "Stroke Width",
|
||||
color: "Color",
|
||||
image: "Image",
|
||||
settings: "Settings",
|
||||
rotation: "Rotation",
|
||||
scale: "Scale",
|
||||
gapX: "Gap X",
|
||||
gapY: "Gap Y",
|
||||
offset: "Offset",
|
||||
emptyLayer: "Empty Layer",
|
||||
aiGroupLayer: "AI Group Layer",
|
||||
textLayer: "Text Layer",
|
||||
rectLayer: "Rect Layer",
|
||||
lineLayer: "Line Layer",
|
||||
ellipseLayer: "Ellipse Layer",
|
||||
triangleLayer: "Triangle Layer",
|
||||
starLayer: "Star Layer",
|
||||
arrowLayer: "Arrow Layer",
|
||||
imageLayer: "Image Layer",
|
||||
mergeLayer: "Merge Layer",
|
||||
rectangle: "Rectangle",
|
||||
line: "Line",
|
||||
arrow: "Arrow",
|
||||
ellipse: "Ellipse",
|
||||
triangle: "Triangle",
|
||||
star: "Star",
|
||||
add: "Add",
|
||||
remove: "Remove",
|
||||
brush: "Brush",
|
||||
erase: "Erase",
|
||||
create: "Create",
|
||||
reset: "Reset"
|
||||
},
|
||||
clipDialog: {
|
||||
title: 'Upload your profile photo',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Save'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,8 @@ export default {
|
||||
userAgreement: '用户协议',
|
||||
privacyPolicy: '隐私政策',
|
||||
view: '查看',
|
||||
remainingNum: '剩余上传头像次数:'
|
||||
remainingNum: '剩余上传头像次数:',
|
||||
notFound: '项目不存在',
|
||||
},
|
||||
Country: {
|
||||
unitedStates: '美国',
|
||||
@@ -128,7 +129,8 @@ export default {
|
||||
confirm: '确认',
|
||||
styleTitle: '设置',
|
||||
createProject: '创建项目',
|
||||
trendingReport: '趋势报告'
|
||||
trendingReport: '趋势报告',
|
||||
reportPlaceholder: '生成一份2026年家具流行趋势报告,内容包括流行风格和设计方向。'
|
||||
},
|
||||
area: {
|
||||
unitedStates: '美国',
|
||||
@@ -153,7 +155,7 @@ export default {
|
||||
quote: '引用',
|
||||
delete: '删除',
|
||||
edit: '编辑',
|
||||
generatingReport:'正在为您生成报告,可能需要几分钟时间,生成期间你可以继续进行其他任务'
|
||||
generatingReport: '正在为您生成报告,可能需要几分钟时间,生成期间你可以继续进行其他任务'
|
||||
},
|
||||
|
||||
// Version Tree
|
||||
@@ -197,5 +199,54 @@ export default {
|
||||
threeModel: {
|
||||
loading: '加载中',
|
||||
download: '下载'
|
||||
},
|
||||
DepthCanvas: {
|
||||
layer: "图层",
|
||||
editDetails: "编辑详情",
|
||||
export: "导出",
|
||||
save: "保存",
|
||||
workbench: "工作台",
|
||||
position: "位置",
|
||||
size: "大小",
|
||||
appearance: "外观",
|
||||
opacity: "透明度",
|
||||
cornerRadius: "圆角半径",
|
||||
strokeWidth: "边框宽度",
|
||||
color: "颜色",
|
||||
image: "图片",
|
||||
settings: "设置",
|
||||
rotation: "旋转角度",
|
||||
scale: "缩放",
|
||||
gapX: "水平间距",
|
||||
gapY: "垂直间距",
|
||||
offset: "偏移量",
|
||||
emptyLayer: "空图层",
|
||||
aiGroupLayer: "智能选区组",
|
||||
textLayer: "文本图层",
|
||||
rectLayer: "矩形图层",
|
||||
lineLayer: "直线图层",
|
||||
ellipseLayer: "椭圆图层",
|
||||
triangleLayer: "三角形图层",
|
||||
starLayer: "五角星图层",
|
||||
arrowLayer: "箭头图层",
|
||||
imageLayer: "图片图层",
|
||||
mergeLayer: "合并图层",
|
||||
rectangle: "矩形",
|
||||
line: "直线",
|
||||
arrow: "箭头",
|
||||
ellipse: "椭圆",
|
||||
triangle: "三角形",
|
||||
star: "五角星",
|
||||
add: "添加",
|
||||
remove: "删除",
|
||||
brush: "画笔",
|
||||
erase: "擦除",
|
||||
create: "创建",
|
||||
reset: "重置"
|
||||
},
|
||||
clipDialog: {
|
||||
title: '上传您的个人资料照片',
|
||||
cancel: '取消',
|
||||
confirm: '保存'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
const isGenerating = ref(false)
|
||||
const isPaused = ref(false) // 标记是否为主动暂停
|
||||
const params = reactive<AgentParamsType>({
|
||||
projectID: projectStore.state.id,
|
||||
projectID: null,
|
||||
message: '',
|
||||
token: userStore.state.token,
|
||||
versionID: '',
|
||||
@@ -170,13 +170,16 @@
|
||||
const abortController = createAbortController()
|
||||
|
||||
// console.log('token---', params.token, '参数---', params)
|
||||
|
||||
params.projectID = projectStore.state.id
|
||||
try {
|
||||
const urlParams = new URLSearchParams<AgentParamsType>({
|
||||
...params,
|
||||
configParams: JSON.stringify(params.configParams)
|
||||
})
|
||||
const BASEURL = import.meta.env.VITE_APP_URL
|
||||
// console.log('params', params)
|
||||
|
||||
// debugger
|
||||
const response = await fetch(`${BASEURL}${chatUrl}?${urlParams.toString()}`, {
|
||||
method: 'GET',
|
||||
signal: abortController.signal
|
||||
@@ -496,6 +499,7 @@
|
||||
let combinedThinkingText = item.reasoning || ''
|
||||
let combinedImageUrl = item.image_url || null
|
||||
let reportName = item.reportName || null
|
||||
let webAddress = item.webAddress || null
|
||||
// 继续往后找连续的 assistant 消息
|
||||
let j = i + 1
|
||||
while (j < dialogue.length && dialogue[j].role === 'assistant') {
|
||||
@@ -508,6 +512,12 @@
|
||||
if (dialogue[j].reportName) {
|
||||
reportName = dialogue[j].reportName
|
||||
}
|
||||
if (dialogue[j].webAddress) {
|
||||
combinedContent += `<slot slot-name="url"></slot>`
|
||||
webAddress = dialogue[j].webAddress
|
||||
// console.log('webAddress22222222222222', dialogue[j].webAddress)
|
||||
// debugger
|
||||
}
|
||||
j++
|
||||
}
|
||||
|
||||
@@ -523,6 +533,7 @@
|
||||
thinkingText: combinedThinkingText,
|
||||
text: combinedContent,
|
||||
image_url: combinedImageUrl,
|
||||
webAddress: !!webAddress ? JSON.parse(webAddress) : null,
|
||||
isUser: false,
|
||||
id: result.length + 1,
|
||||
sessionId: sessionId
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
<span>{{ content.webAddress?.length }} web pages have been retrieved.</span>
|
||||
</div>
|
||||
</div>
|
||||
<Pause v-show="showStop && isLast" />
|
||||
<Pause v-show="showStop" :key="props.content.createTime" />
|
||||
<div
|
||||
v-show="!content.streaming"
|
||||
class="operate flex"
|
||||
@@ -136,6 +136,14 @@
|
||||
isLast: Boolean
|
||||
}>()
|
||||
|
||||
// watch(
|
||||
// () => props.content,
|
||||
// (newVal) => {
|
||||
// console.log('props', newVal)
|
||||
// },
|
||||
// { deep: true,immediate: true }
|
||||
// )
|
||||
|
||||
const emit = defineEmits(['regenerate'])
|
||||
|
||||
const userAvatar = computed(() => {
|
||||
@@ -321,12 +329,14 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
.message-context {
|
||||
line-height: 2rem;
|
||||
font-size: 1.4rem;
|
||||
width: 82%;
|
||||
word-break: break-word;
|
||||
}
|
||||
&.is-user .message-context {
|
||||
width: fit-content;
|
||||
|
||||
@@ -13,11 +13,12 @@
|
||||
width: 100%;
|
||||
height: 3.6rem;
|
||||
line-height: 3.6rem;
|
||||
column-gap: 0.6rem;
|
||||
padding: 0 1.2rem;
|
||||
column-gap: 0.6rem;
|
||||
padding: 0 1.2rem;
|
||||
background-color: #fffcf4;
|
||||
border-radius: 0.4rem;
|
||||
margin-top: 1rem;
|
||||
border-radius: 0.4rem;
|
||||
margin-top: 1rem;
|
||||
color: #ff7a51;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
@@ -30,22 +31,18 @@
|
||||
rgba(233, 121, 60, 0.3) 1.61%,
|
||||
rgba(255, 207, 144, 0.3) 101.01%
|
||||
);
|
||||
-webkit-mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
mask:
|
||||
linear-gradient(#fff 0 0) content-box,
|
||||
linear-gradient(#fff 0 0);
|
||||
-webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
-webkit-mask-composite: xor;
|
||||
mask-composite: exclude;
|
||||
}
|
||||
|
||||
.c-svg{
|
||||
width: initial;
|
||||
.svg-icon{
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
}
|
||||
.c-svg {
|
||||
width: initial;
|
||||
.svg-icon {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="report-card" :class="{ 'is-url': isUrl, 'is-sketch': isSketch }">
|
||||
<div class="report-card-header">
|
||||
<span v-if="!isUrl && !isSketch">{{ title || '' }}</span>
|
||||
<div v-else class="web-sources flex align-center">
|
||||
<!-- <span v-if="!isUrl && !isSketch">{{ title || '' }}</span> -->
|
||||
<div class="web-sources flex align-center">
|
||||
<span>{{ title || '' }}</span>
|
||||
<img src="@/assets/images/link.png" class="link-icon" />
|
||||
</div>
|
||||
@@ -35,16 +35,19 @@
|
||||
.report-card {
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
margin: 2.4rem 0;
|
||||
height: 11.2rem;
|
||||
margin: 1.2rem 0 0;
|
||||
min-height: 11.2rem;
|
||||
background: url('@/assets/images/report-card.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 2.9rem;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
margin-bottom: 0;
|
||||
&.is-url {
|
||||
background: url('@/assets/images/link-card.png') no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin: 2.4rem 0;
|
||||
}
|
||||
&.is-sketch {
|
||||
background: url('@/assets/images/sketch-card.png') no-repeat;
|
||||
@@ -61,7 +64,7 @@
|
||||
|
||||
&-header {
|
||||
font-family: 'Medium';
|
||||
font-size: 1.6rem;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1.3rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -80,7 +83,7 @@
|
||||
&-content {
|
||||
font-family: 'Regular';
|
||||
font-weight: 300;
|
||||
font-size: 1.6rem;
|
||||
font-size: 1.2rem;
|
||||
color: #7c7c7c;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +36,13 @@
|
||||
import { useProjectStore } from '@/stores'
|
||||
import { getProjectInfo } from '@/api/agent'
|
||||
import { clearNodeChat, getNodeAncestors } from '@/api/versitonTree'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
const projectStore = useProjectStore()
|
||||
|
||||
const previewRef = ref(null)
|
||||
@@ -90,6 +93,11 @@
|
||||
const handleGetProjectInfoAndHistory = () => {
|
||||
handleOpenSketch()
|
||||
getProjectInfo({ id: route.params.id }).then((res) => {
|
||||
if(!res) {
|
||||
router.push({ name: 'mainInput' })
|
||||
ElMessage.warning(t('Home.notFound'))
|
||||
return
|
||||
}
|
||||
if (res) agentRef.value.setChatInfo(res)
|
||||
let data = res?.project || res
|
||||
if (data?.latestNodeId) data.nodeId = data.latestNodeId
|
||||
@@ -125,6 +133,7 @@
|
||||
projectStore.clearProject()
|
||||
if (newVal) {
|
||||
handleGetProjectInfoAndHistory()
|
||||
MyEvent.emit('projectChange')
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
<img
|
||||
:src="image.url || image"
|
||||
class="preview-image"
|
||||
@click="previewImage(image.url)"
|
||||
@click="previewImage(image.url || image)"
|
||||
/>
|
||||
<div class="image-remove-btn" @click="removeImage(index)">
|
||||
<div class="image-remove-btn" @click="removeImage(index, image)">
|
||||
<SvgIcon name="delete" size="16" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -305,8 +305,16 @@
|
||||
}
|
||||
|
||||
// 移除图片
|
||||
const removeImage = (index: number) => {
|
||||
uploadedImages.value.splice(index, 1)
|
||||
const removeImage = (index: number, item: any) => {
|
||||
if (quoteList.value.includes(item)) {
|
||||
const quoteIndex = quoteList.value.indexOf(item)
|
||||
if (quoteIndex > -1) {
|
||||
quoteList.value.splice(quoteIndex, 1)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
uploadedImages.value.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const styleKeys: string[] = [
|
||||
@@ -472,8 +480,7 @@
|
||||
customPlaceholder.value = placeholderSpan
|
||||
|
||||
// 打字机效果显示placeholder文本
|
||||
const placeholderText =
|
||||
'Generate a furniture trending report for 2026, including popular styles and design directions.'
|
||||
const placeholderText = t('Input.reportPlaceholder')
|
||||
typeWriterEffect(placeholderSpan, placeholderText)
|
||||
|
||||
const removePlaceholderOnInput = () => {
|
||||
@@ -487,12 +494,12 @@
|
||||
editorRef.value?.addEventListener('input', removePlaceholderOnInput)
|
||||
}
|
||||
|
||||
const toogltReportTag = () => {
|
||||
const toogltReportTag = (clear = false) => {
|
||||
stopTypewriter() // 移除标签时停止打字机效果
|
||||
// 清理掉已被删除的标签引用(从 DOM 中移除的元素)
|
||||
reportTags.value = reportTags.value.filter((tag) => tag.parentNode !== null)
|
||||
|
||||
if (reportTags.value.length > 0) {
|
||||
if (reportTags.value.length > 0 ) {
|
||||
// 移除所有标签及其关联的零宽空格
|
||||
reportTags.value.forEach((tag) => {
|
||||
if (
|
||||
@@ -715,14 +722,6 @@
|
||||
})
|
||||
})
|
||||
|
||||
// 初始化编辑器高度
|
||||
onMounted(() => {
|
||||
MyEvent.add('quote', handleQuote)
|
||||
nextTick(() => {
|
||||
autoResizeEditor()
|
||||
})
|
||||
})
|
||||
|
||||
const typeValue = ref<string>('')
|
||||
const areaValue = ref<string>('')
|
||||
const styleValue = ref<string>('')
|
||||
@@ -815,10 +814,31 @@
|
||||
}
|
||||
|
||||
const handleQuote = (url: string) => {
|
||||
quoteList.value.push(url)
|
||||
const hasQuoted = quoteList.value.includes(url)
|
||||
if (hasQuoted) return
|
||||
quoteList.value[0] = url
|
||||
}
|
||||
|
||||
const handleInitInput = () => {
|
||||
inputValue.value = ''
|
||||
uploadedImages.value = []
|
||||
quoteList.value = []
|
||||
toogltReportTag(true)
|
||||
if (editorRef.value) {
|
||||
editorRef.value.innerHTML = ''
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
MyEvent.add('quote', handleQuote)
|
||||
MyEvent.add('projectChange', handleInitInput)
|
||||
nextTick(() => {
|
||||
autoResizeEditor()
|
||||
})
|
||||
})
|
||||
onUnmounted(() => {
|
||||
MyEvent.remove('quote', handleQuote)
|
||||
MyEvent.remove('projectChange', handleInitInput)
|
||||
})
|
||||
// 暴露方法给父组件
|
||||
defineExpose({
|
||||
@@ -1057,10 +1077,11 @@
|
||||
width: 13rem;
|
||||
color: #fff;
|
||||
border-radius: 4.2rem;
|
||||
font-family: 'MSemiBold';
|
||||
font-family: 'SemiBold';
|
||||
font-weight: 600;
|
||||
font-size: 1.28rem;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
column-gap: 0.3rem;
|
||||
.shining-icon {
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
<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="icon"><svg-icon name="history" size="16" /></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>
|
||||
</div>
|
||||
<div class="history-list" v-show="!isCollapse && showHistory">
|
||||
<div class="history-list mini-scrollbar" v-show="!isCollapse && showHistory">
|
||||
<div v-for="item in list" :key="item.name" class="history-item">
|
||||
<div v-if="item.title" class="title">{{ item.name }}</div>
|
||||
<div
|
||||
@@ -54,7 +54,7 @@
|
||||
v-model:visible="item.visible"
|
||||
>
|
||||
<template #reference>
|
||||
<span @click.stop="item.visible = !item.visible" class="icon">
|
||||
<span @click.stop="openPopover(item)" class="icon">
|
||||
<svg-icon name="more" size="16" />
|
||||
</span>
|
||||
</template>
|
||||
@@ -207,6 +207,12 @@
|
||||
}
|
||||
})
|
||||
}
|
||||
const openPopover = (item: any) => {
|
||||
list.value.forEach((item: any) => {
|
||||
item.visible = false
|
||||
})
|
||||
item.visible = !item.visible
|
||||
}
|
||||
onMounted(() => {
|
||||
MyEvent.add('newTitle', replaceTitle)
|
||||
})
|
||||
@@ -307,7 +313,8 @@
|
||||
> .history-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
width: 23.2rem;
|
||||
width: 26.4rem;
|
||||
padding: 0 1.5rem;
|
||||
margin: 1rem auto 0;
|
||||
> .history-item {
|
||||
width: 100%;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
<div class="label">{{ $t('Home.logoutDevice') }}</div>
|
||||
<button class="logout-btn" @click="logout">{{ $t('Home.logout') }}</button>
|
||||
</div>
|
||||
<clip-dialog ref="clipDialogRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -41,6 +42,8 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { uploadImage } from '@/api/upload'
|
||||
import { UpdateUserAvatar, getAvatarLimit } from '@/api/user'
|
||||
import clipDialog from '@/components/clipDialog.vue'
|
||||
import { fileToBase64, base64Tofile } from '../../../components/Canvas/tools/tools'
|
||||
const router = useRouter()
|
||||
const { locale } = useI18n()
|
||||
const userInfoStore = useUserInfoStore()
|
||||
@@ -50,6 +53,7 @@
|
||||
{ label: '中文', value: 'CHINESE_SIMPLIFIED' }
|
||||
])
|
||||
const remainingNum = ref(0)
|
||||
const clipDialogRef = ref<typeof clipDialog>()
|
||||
const changeLang = (value: string) => {
|
||||
locale.value = value
|
||||
localStorage.setItem('language', value)
|
||||
@@ -72,13 +76,17 @@
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = 'image/png, image/jpeg, image/jpg'
|
||||
input.addEventListener('change', (e) => {
|
||||
input.addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0]
|
||||
const formData = new FormData()
|
||||
formData.append('avatar', file)
|
||||
UpdateUserAvatar(formData).then((res) => {
|
||||
userInfoStore.updateUserInfo({avatar: res})
|
||||
getAvatarLimitNum()
|
||||
let base64Img = await fileToBase64(file)
|
||||
clipDialogRef.value?.open(base64Img).then((base64: string)=>{
|
||||
const fileData = base64Tofile(base64,file.name)
|
||||
const formData = new FormData()
|
||||
formData.append('avatar', fileData)
|
||||
UpdateUserAvatar(formData).then((res) => {
|
||||
userInfoStore.updateUserInfo({avatar: res})
|
||||
getAvatarLimitNum()
|
||||
})
|
||||
})
|
||||
})
|
||||
input.click()
|
||||
|
||||
@@ -11,12 +11,12 @@
|
||||
import { useRoute } from 'vue-router'
|
||||
const route = useRoute()
|
||||
const url =
|
||||
'https://www.minio-api.aida.com.hk/fida-test/furniture/sketches/1a48ed3a-1faa-4fcd-bf07-765dba1702c5.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=admin%2F20260320%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20260320T020948Z&X-Amz-Expires=604800&X-Amz-SignedHeaders=host&X-Amz-Signature=7dc192bac887bce7b02c99d7037c08d9d684310f00add9b0e63b74b36ee63d37'
|
||||
'https://minio-api.aida.com.hk/fida-public-bucket/furniture/sketches/e3082a38-55d2-4313-ad53-55aad715cf67.png'
|
||||
const openCanvas = () => {
|
||||
myEvent.emit('openFlowCanvas', { url })
|
||||
}
|
||||
const openDepthCanvas = () => {
|
||||
myEvent.emit('openDepthCanvas', { url, canvasId: '69c34539ce996b52f07e625f' })
|
||||
myEvent.emit('openDepthCanvas', { url, canvasId: '' })
|
||||
}
|
||||
onMounted(() => {
|
||||
if (route.query.depth) {
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
}
|
||||
})
|
||||
} else {
|
||||
data.vibe = JSON.parse(data.vibe)
|
||||
onSubmit(data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<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" />
|
||||
<img :src="v.imageUrl" draggable="false" />
|
||||
<div class="active" v-show="v.active">
|
||||
<span>{{ v.title }}</span>
|
||||
<span>{{ v.styleName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -22,28 +22,35 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { GetUserStyleImages } from '@/api/user'
|
||||
const router = useRouter()
|
||||
const emit = defineEmits(['next'])
|
||||
const list = ref([
|
||||
{ id: 1, url: '/image/nuic/style-1.png', title: '凳子', active: false },
|
||||
{ id: 2, url: '/image/nuic/style-2.png', title: '沙发', active: false },
|
||||
{ id: 3, url: '/image/nuic/style-3.png', title: '凳子', active: false },
|
||||
{ id: 4, url: '/image/nuic/style-4.png', title: '桌子', active: false },
|
||||
{ id: 5, url: '/image/nuic/style-5.png', title: '桌子', active: false },
|
||||
{ id: 6, url: '/image/nuic/style-6.png', title: '桌子', active: false },
|
||||
{ id: 7, url: '/image/nuic/style-7.png', title: '沙发', active: false },
|
||||
{ id: 8, url: '/image/nuic/style-8.png', title: '桌子', active: false }
|
||||
])
|
||||
const list = ref([])
|
||||
const pageSize = ref(8)
|
||||
const pageNum = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const onNext = () => {
|
||||
const data = {
|
||||
vibe: list.value
|
||||
.filter((v) => v.active)
|
||||
.map((v) => v.id)
|
||||
.join(',')
|
||||
vibe: JSON.stringify(list.value.filter((v) => v.active).map((v) => v.id))
|
||||
}
|
||||
emit('next', data)
|
||||
}
|
||||
const onLoadMore = () => {}
|
||||
const onLoadMore = () => {
|
||||
GetUserStyleImages({
|
||||
pageNum: pageNum.value,
|
||||
pageSize: pageSize.value
|
||||
}).then((res) => {
|
||||
if (!res) return
|
||||
list.value = res.images.map((v) => ({
|
||||
...v,
|
||||
active: false
|
||||
}))
|
||||
totalPages.value = res.totalPages
|
||||
pageNum.value++
|
||||
if (pageNum.value > totalPages.value) pageNum.value = 1
|
||||
})
|
||||
}
|
||||
onLoadMore()
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
||||
Reference in New Issue
Block a user