This commit is contained in:
parent
66d4805935
commit
5adfc94746
|
|
@ -0,0 +1,546 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="局部修改"
|
||||
width="90%"
|
||||
:fullscreen="isFullscreen"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="canvas-editor-container">
|
||||
<div class="canvas-toolbar">
|
||||
<div class="toolbar-section">
|
||||
<span class="toolbar-label">画笔颜色:</span>
|
||||
<div class="color-picker">
|
||||
<div
|
||||
v-for="color in colors"
|
||||
:key="color"
|
||||
:class="['color-option', { active: currentColor === color }]"
|
||||
:style="{ backgroundColor: color }"
|
||||
@click="selectColor(color)"
|
||||
></div>
|
||||
<el-color-picker
|
||||
v-model="customColor"
|
||||
size="small"
|
||||
@change="selectCustomColor"
|
||||
class="custom-color-picker"
|
||||
></el-color-picker>
|
||||
</div>
|
||||
</div>
|
||||
<div class="toolbar-section">
|
||||
<span class="toolbar-label">画笔大小:</span>
|
||||
<el-slider
|
||||
v-model="brushSize"
|
||||
:min="1"
|
||||
:max="50"
|
||||
style="width: 120px"
|
||||
/>
|
||||
</div>
|
||||
<div class="toolbar-section">
|
||||
<el-button
|
||||
:type="isEraser ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="toggleEraser"
|
||||
>
|
||||
<el-icon><Delete /></el-icon>
|
||||
橡皮擦
|
||||
</el-button>
|
||||
<el-button
|
||||
type="warning"
|
||||
size="small"
|
||||
@click="clearCanvas"
|
||||
>
|
||||
<el-icon><RefreshLeft /></el-icon>
|
||||
清空
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
size="small"
|
||||
@click="handleSave"
|
||||
>
|
||||
<el-icon><Check /></el-icon>
|
||||
确定
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas-wrapper" ref="canvasWrapper">
|
||||
<div v-if="imageLoading" class="loading-overlay">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载图片中...</span>
|
||||
</div>
|
||||
<div v-if="imageLoadError" class="error-overlay">
|
||||
<el-icon><Warning /></el-icon>
|
||||
<p>图片加载失败</p>
|
||||
<el-button type="primary" size="small" @click="retryLoadImage">重试</el-button>
|
||||
<el-button size="small" @click="dialogVisible = false">关闭</el-button>
|
||||
</div>
|
||||
<canvas
|
||||
v-show="!imageLoading && !imageLoadError"
|
||||
ref="canvas"
|
||||
@mousedown="startDrawing"
|
||||
@mousemove="draw"
|
||||
@mouseup="stopDrawing"
|
||||
@mouseleave="stopDrawing"
|
||||
@touchstart="handleTouchStart"
|
||||
@touchmove="handleTouchMove"
|
||||
@touchend="handleTouchEnd"
|
||||
></canvas>
|
||||
</div>
|
||||
<div class="edit-textarea-container">
|
||||
<el-input
|
||||
v-model="editContent"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入要修改的内容"
|
||||
class="edit-textarea"
|
||||
></el-input>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { Delete, RefreshLeft, Check, Loading, Warning } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
imageUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:visible', 'add-prompt-card', 'close'])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const isFullscreen = ref(false)
|
||||
const canvas = ref(null)
|
||||
const canvasWrapper = ref(null)
|
||||
const ctx = ref(null)
|
||||
const isDrawing = ref(false)
|
||||
const isEraser = ref(false)
|
||||
const currentColor = ref('#6B46C1')
|
||||
const customColor = ref('#6B46C1')
|
||||
const brushSize = ref(5)
|
||||
const imageLoading = ref(false)
|
||||
const imageLoadError = ref(false)
|
||||
const loadRetryCount = ref(0)
|
||||
const maxRetryCount = 3
|
||||
const editContent = ref('')
|
||||
const colors = [
|
||||
'#6B46C1',
|
||||
'#EF4444',
|
||||
'#F59E0B',
|
||||
'#10B981',
|
||||
'#3B82F6',
|
||||
'#8B5CF6',
|
||||
'#EC4899',
|
||||
'#1F2937'
|
||||
]
|
||||
|
||||
let lastX = 0
|
||||
let lastY = 0
|
||||
let backgroundImage = null
|
||||
|
||||
watch(() => props.visible, (val) => {
|
||||
dialogVisible.value = val
|
||||
if (val) {
|
||||
nextTick(() => {
|
||||
initCanvas()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
watch(dialogVisible, (val) => {
|
||||
emit('update:visible', val)
|
||||
})
|
||||
|
||||
const initCanvas = async () => {
|
||||
if (!canvas.value || !canvasWrapper.value) return
|
||||
|
||||
imageLoading.value = true
|
||||
imageLoadError.value = false
|
||||
|
||||
try {
|
||||
const img = new Image()
|
||||
img.crossOrigin = 'anonymous'
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onload = () => resolve()
|
||||
img.onerror = () => reject(new Error('Failed to load image'))
|
||||
img.src = props.imageUrl
|
||||
})
|
||||
|
||||
const canvasEl = canvas.value
|
||||
|
||||
canvasEl.width = img.width
|
||||
canvasEl.height = img.height
|
||||
|
||||
ctx.value = canvasEl.getContext('2d')
|
||||
|
||||
backgroundImage = img
|
||||
|
||||
drawImage()
|
||||
imageLoading.value = false
|
||||
loadRetryCount.value = 0
|
||||
} catch (error) {
|
||||
console.error('Failed to load image:', props.imageUrl, error)
|
||||
imageLoading.value = false
|
||||
imageLoadError.value = true
|
||||
ElMessage.error('图片加载失败,请重试')
|
||||
}
|
||||
}
|
||||
|
||||
const retryLoadImage = () => {
|
||||
if (loadRetryCount.value < maxRetryCount) {
|
||||
loadRetryCount.value++
|
||||
ElMessage.info(`正在重试加载图片 (${loadRetryCount.value}/${maxRetryCount})`)
|
||||
initCanvas()
|
||||
} else {
|
||||
ElMessage.warning('已达到最大重试次数,请稍后再试')
|
||||
}
|
||||
}
|
||||
|
||||
const drawImage = () => {
|
||||
if (!ctx.value || !backgroundImage || !canvas.value) return
|
||||
|
||||
const canvasEl = canvas.value
|
||||
const img = backgroundImage
|
||||
|
||||
ctx.value.fillStyle = '#FFFFFF'
|
||||
ctx.value.fillRect(0, 0, canvasEl.width, canvasEl.height)
|
||||
|
||||
ctx.value.drawImage(img, 0, 0, img.width, img.height)
|
||||
}
|
||||
|
||||
const selectColor = (color) => {
|
||||
currentColor.value = color
|
||||
isEraser.value = false
|
||||
}
|
||||
|
||||
const selectCustomColor = (color) => {
|
||||
if (color) {
|
||||
currentColor.value = color
|
||||
isEraser.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const toggleEraser = () => {
|
||||
isEraser.value = !isEraser.value
|
||||
}
|
||||
|
||||
const clearCanvas = () => {
|
||||
if (!ctx.value || !canvas.value) return
|
||||
|
||||
ctx.value.fillStyle = '#FFFFFF'
|
||||
ctx.value.fillRect(0, 0, canvas.value.width, canvas.value.height)
|
||||
|
||||
if (backgroundImage) {
|
||||
ctx.value.drawImage(backgroundImage, 0, 0, backgroundImage.width, backgroundImage.height)
|
||||
}
|
||||
}
|
||||
|
||||
const getCanvasCoordinates = (clientX, clientY) => {
|
||||
const rect = canvas.value.getBoundingClientRect()
|
||||
const scaleX = canvas.value.width / rect.width
|
||||
const scaleY = canvas.value.height / rect.height
|
||||
|
||||
return {
|
||||
x: (clientX - rect.left) * scaleX,
|
||||
y: (clientY - rect.top) * scaleY
|
||||
}
|
||||
}
|
||||
|
||||
const startDrawing = (e) => {
|
||||
isDrawing.value = true
|
||||
const coords = getCanvasCoordinates(e.clientX, e.clientY)
|
||||
lastX = coords.x
|
||||
lastY = coords.y
|
||||
}
|
||||
|
||||
const draw = (e) => {
|
||||
if (!isDrawing.value || !ctx.value) return
|
||||
|
||||
const coords = getCanvasCoordinates(e.clientX, e.clientY)
|
||||
|
||||
ctx.value.beginPath()
|
||||
ctx.value.moveTo(lastX, lastY)
|
||||
ctx.value.lineTo(coords.x, coords.y)
|
||||
ctx.value.strokeStyle = isEraser.value ? '#FFFFFF' : currentColor.value
|
||||
ctx.value.lineWidth = brushSize.value
|
||||
ctx.value.lineCap = 'round'
|
||||
ctx.value.lineJoin = 'round'
|
||||
ctx.value.stroke()
|
||||
|
||||
lastX = coords.x
|
||||
lastY = coords.y
|
||||
}
|
||||
|
||||
const stopDrawing = () => {
|
||||
isDrawing.value = false
|
||||
}
|
||||
|
||||
const handleTouchStart = (e) => {
|
||||
e.preventDefault()
|
||||
const touch = e.touches[0]
|
||||
const coords = getCanvasCoordinates(touch.clientX, touch.clientY)
|
||||
lastX = coords.x
|
||||
lastY = coords.y
|
||||
isDrawing.value = true
|
||||
}
|
||||
|
||||
const handleTouchMove = (e) => {
|
||||
e.preventDefault()
|
||||
if (!isDrawing.value || !ctx.value) return
|
||||
|
||||
const touch = e.touches[0]
|
||||
const coords = getCanvasCoordinates(touch.clientX, touch.clientY)
|
||||
|
||||
ctx.value.beginPath()
|
||||
ctx.value.moveTo(lastX, lastY)
|
||||
ctx.value.lineTo(coords.x, coords.y)
|
||||
ctx.value.strokeStyle = isEraser.value ? '#FFFFFF' : currentColor.value
|
||||
ctx.value.lineWidth = brushSize.value
|
||||
ctx.value.lineCap = 'round'
|
||||
ctx.value.lineJoin = 'round'
|
||||
ctx.value.stroke()
|
||||
|
||||
lastX = coords.x
|
||||
lastY = coords.y
|
||||
}
|
||||
|
||||
const handleTouchEnd = (e) => {
|
||||
e.preventDefault()
|
||||
isDrawing.value = false
|
||||
}
|
||||
|
||||
const handleSave = () => {
|
||||
const dataUrl = canvas.value.toDataURL('image/png')
|
||||
emit('add-prompt-card', dataUrl,editContent.value)
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
if (dialogVisible.value) {
|
||||
nextTick(() => {
|
||||
initCanvas()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
if (window.innerWidth < 768) {
|
||||
isFullscreen.value = true
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.canvas-editor-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.canvas-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
background: #f8fafc;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e2e8f0;
|
||||
}
|
||||
|
||||
.toolbar-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.toolbar-label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1F2937;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.color-option {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.color-option:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.color-option.active {
|
||||
border-color: #1F2937;
|
||||
box-shadow: 0 0 0 2px rgba(107, 70, 193, 0.3);
|
||||
}
|
||||
|
||||
.custom-color-picker {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.custom-color-picker :deep(.el-color-picker__trigger) {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.custom-color-picker :deep(.el-color-picker__color) {
|
||||
border-radius: 50%;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
background: #f3f4f6;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid #e2e8f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loading-overlay,
|
||||
.error-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
background: #f3f4f6;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.loading-overlay .el-icon {
|
||||
font-size: 32px;
|
||||
color: #6B46C1;
|
||||
}
|
||||
|
||||
.loading-overlay span {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.error-overlay .el-icon {
|
||||
font-size: 48px;
|
||||
color: #EF4444;
|
||||
}
|
||||
|
||||
.error-overlay p {
|
||||
font-size: 16px;
|
||||
color: #1F2937;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
canvas {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
width: auto;
|
||||
height: auto;
|
||||
cursor: crosshair;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.edit-textarea-container {
|
||||
width: 100%;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.canvas-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.toolbar-section {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.color-picker {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1024px) {
|
||||
.canvas-wrapper {
|
||||
height: 450px;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] .canvas-toolbar {
|
||||
background: #1f1f1f;
|
||||
border-color: #303030;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .toolbar-label {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .canvas-wrapper {
|
||||
background: #141414;
|
||||
border-color: #303030;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .loading-overlay,
|
||||
[data-theme="dark"] .error-overlay {
|
||||
background: #141414;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .loading-overlay span,
|
||||
[data-theme="dark"] .error-overlay p {
|
||||
color: #e5e7eb;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .color-option.active {
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
ref="textInputRef"
|
||||
v-model="textInputValue"
|
||||
class="text-input-area"
|
||||
placeholder="请输入文本内容..."
|
||||
:placeholder="t('modelModal.textInputPlaceholder')"
|
||||
rows="4"
|
||||
@keydown.enter.ctrl="handleTextInputConfirm"
|
||||
@keydown.esc="handleTextInputCancel"
|
||||
|
|
@ -74,6 +74,10 @@
|
|||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="scene graph" @click="handleTextcjt" @touchend.prevent="handleTextcjt">
|
||||
<el-icon class="btn-icon"><Grid /></el-icon>
|
||||
</button>
|
||||
<button v-if="!(props.cardData.imgyt)" class="control-button share-btn" title="Picture board editor" @click="handlePartialEdit" @touchend.prevent="handlePartialEdit">
|
||||
<el-icon class="btn-icon"><EditPen /></el-icon>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -91,7 +95,7 @@ import { GiminiServer } from '@deotaland/utils';
|
|||
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
||||
// import cz2 from '@/assets/material/cz2.png';
|
||||
// 引入Element Plus图标库和组件
|
||||
import { Cpu, ChatDotRound, CloseBold,Grid,View } from '@element-plus/icons-vue'
|
||||
import { Cpu, ChatDotRound, CloseBold,Grid,View,EditPen } from '@element-plus/icons-vue'
|
||||
import { ElIcon,ElMessage,ElSkeleton,ElImage } from 'element-plus'
|
||||
const { t } = useI18n();
|
||||
const formData = ref({
|
||||
|
|
@ -221,8 +225,10 @@ const props = defineProps({
|
|||
});
|
||||
|
||||
// 定义事件
|
||||
const emit = defineEmits(['generate-model-requested', 'create-new-card','create-prompt-card','delete','preview-image','customize-to-home']);
|
||||
|
||||
const emit = defineEmits(['generate-model-requested', 'create-new-card','create-prompt-card','delete','preview-image','customize-to-home','handlePartialEdit']);
|
||||
const handlePartialEdit = ()=>{
|
||||
emit('handlePartialEdit', formData.value.internalImageUrl);
|
||||
}
|
||||
// 处理图片生成
|
||||
const handleGenerateImage = async () => {
|
||||
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ export default {
|
|||
}
|
||||
},
|
||||
modelModal: {
|
||||
customizeToHome: '定制到家'
|
||||
customizeToHome: '定制到家',
|
||||
textInputPlaceholder: '请输入调整内容,例如:更改角色表情'
|
||||
},
|
||||
modelCard: {
|
||||
generateModelButton: '生成模型',
|
||||
|
|
@ -1446,7 +1447,8 @@ export default {
|
|||
}
|
||||
},
|
||||
modelModal: {
|
||||
customizeToHome: 'Customize to Home'
|
||||
customizeToHome: 'Customize to Home',
|
||||
textInputPlaceholder: 'Please enter adjustment content, e.g. change character expression'
|
||||
},
|
||||
modelCard: {
|
||||
generateModelButton: 'Generate Model',
|
||||
|
|
|
|||
|
|
@ -55,6 +55,8 @@
|
|||
class="delete-button"
|
||||
@click.stop="handleDeleteCard(index)"
|
||||
@mousedown.stop
|
||||
@touchstart.stop
|
||||
@touchend.stop
|
||||
title="删除卡片"
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
|
@ -65,6 +67,7 @@
|
|||
<!-- 根据卡片类型显示不同组件 -->
|
||||
<IPCard
|
||||
:combinedPromptJson="combinedPromptJson"
|
||||
@handlePartialEdit="(imageUrl) => handlePartialEdit(imageUrl, index)"
|
||||
@customize-to-home="handleCustomizeToHome(index)"
|
||||
@preview-image="handlePreviewImage"
|
||||
@generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)"
|
||||
|
|
@ -120,7 +123,11 @@
|
|||
:initialIndex="currentImageIndex"
|
||||
@close="showImagePreview = false"
|
||||
/>
|
||||
|
||||
<CanvasEditor
|
||||
v-model:visible="canvasEditorVisible"
|
||||
:image-url="canvasEditorImageUrl"
|
||||
@add-prompt-card="handleCanvasSave"
|
||||
/>
|
||||
<!-- 测试侧边栏动画的按钮 -->
|
||||
<!-- <button
|
||||
class="test-animation-btn"
|
||||
|
|
@ -154,12 +161,13 @@ import HeaderComponent from '../../components/HeaderComponent/HeaderComponent.vu
|
|||
import GuideModal from '../../components/GuideModal/index.vue';
|
||||
import ImagePreviewModal from '../../components/ImagePreviewModal/index.vue';
|
||||
import {useRoute,useRouter} from 'vue-router';
|
||||
import {MeshyServer,GiminiServer} from '@deotaland/utils';
|
||||
import {MeshyServer,GiminiServer,FileServer} from '@deotaland/utils';
|
||||
import OrderProcessModal from '../../components/OrderProcessModal/index.vue';
|
||||
import PurchaseModal from '../../components/PurchaseModal/index.vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import {Project} from './index';
|
||||
import {ModernHome} from '../ModernHome/index.js'
|
||||
const fileServer = new FileServer();
|
||||
const modernHome = new ModernHome();
|
||||
const Limits = ref({
|
||||
generateCount: 0,
|
||||
|
|
@ -173,6 +181,8 @@ const selectedModel = ref(null);
|
|||
const showImportModal = ref(false);
|
||||
const importUrl = ref('https://xiaozhi.me/console/agents');
|
||||
const showGuideModal = ref(false);
|
||||
const canvasEditorVisible = ref(false);//画布编辑弹窗是否可见
|
||||
const canvasEditorImageUrl = ref('');//画布编辑弹窗图片url
|
||||
// 图片预览弹窗相关状态
|
||||
const showImagePreview = ref(false);
|
||||
const previewImages = ref([]);
|
||||
|
|
@ -191,6 +201,24 @@ const getGenerateCount = async ()=>{
|
|||
// Limits.value.generateCount = data[0].model_count;
|
||||
// Limits.value.modelCount = data[1].model_count;
|
||||
}
|
||||
const handlePartialEdit = (imageUrl, index) => {
|
||||
canvasEditorImageUrl.value = imageUrl;
|
||||
canvasEditorVisible.value = true;
|
||||
}
|
||||
// 处理画布保存事件
|
||||
const handleCanvasSave = (editedImageUrl,editContent) => {
|
||||
fileServer.uploadFile(editedImageUrl).then((url) => {
|
||||
const newCard = createSmartCard({
|
||||
diyPromptImg:url,
|
||||
diyPromptText:editContent,
|
||||
status:'loading',
|
||||
type:'image',
|
||||
// inspirationImage:cardData.inspirationImage||'',
|
||||
});
|
||||
// console.log(newCard,'newCardnewCard');
|
||||
cards.value.push(newCard);
|
||||
})
|
||||
}
|
||||
// 多个可拖动元素的数据
|
||||
const cards = ref([
|
||||
|
||||
|
|
@ -429,12 +457,9 @@ const createSmartCard = (cardConfig,index=null) => {
|
|||
// 获取当前最高层级的卡片
|
||||
// const highestCard;
|
||||
// 获取当前最高层级的卡片(按 zIndex 最大值)
|
||||
let highestCard;
|
||||
if(index){
|
||||
highestCard = cards.value[index]
|
||||
}else{
|
||||
highestCard = cards.value[cards.value.length-1]
|
||||
}
|
||||
let highestCard = cards.value.reduce((prev, current) =>
|
||||
prev.zIndex > current.zIndex ? prev : current, {});
|
||||
|
||||
// 计算位置在最高层级卡片右侧
|
||||
const position = highestCard ?
|
||||
positionCalculator.calculateRightSidePosition(highestCard, scale.value) :
|
||||
|
|
@ -513,10 +538,8 @@ const handleCreatePromptCard = (index,params) => {
|
|||
type:'image',
|
||||
// inspirationImage:cardData.inspirationImage||'',
|
||||
},index);
|
||||
console.log(newCard,'newCardnewCard');
|
||||
// 添加到卡片数组
|
||||
cards.value.push(newCard);
|
||||
console.log(cards.value);
|
||||
}
|
||||
// 处理图片生成请求
|
||||
const handleGenerateRequested = async (params) => {
|
||||
|
|
@ -1358,10 +1381,12 @@ html.dark .draggable-element:hover {
|
|||
/* 删除按钮样式 */
|
||||
.delete-button {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -10px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
top: -12px;
|
||||
right: -12px;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
min-width: 36px;
|
||||
min-height: 36px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #6B46C1 0%, #553C9A 50%, #44337A 100%);
|
||||
|
|
@ -1382,6 +1407,8 @@ html.dark .draggable-element:hover {
|
|||
transform: scale(0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
overflow: hidden;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.delete-button::before {
|
||||
|
|
@ -1534,5 +1561,27 @@ p {
|
|||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 移动端删除按钮始终可见且更大 */
|
||||
.delete-button {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
transform: scale(1) !important;
|
||||
width: 40px !important;
|
||||
height: 40px !important;
|
||||
min-width: 40px !important;
|
||||
min-height: 40px !important;
|
||||
top: -14px !important;
|
||||
right: -14px !important;
|
||||
box-shadow:
|
||||
0 4px 12px rgba(107, 70, 193, 0.6),
|
||||
0 2px 6px rgba(0, 0, 0, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3) !important;
|
||||
}
|
||||
|
||||
.delete-button svg {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
Loading…
Reference in New Issue