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"
|
ref="textInputRef"
|
||||||
v-model="textInputValue"
|
v-model="textInputValue"
|
||||||
class="text-input-area"
|
class="text-input-area"
|
||||||
placeholder="请输入文本内容..."
|
:placeholder="t('modelModal.textInputPlaceholder')"
|
||||||
rows="4"
|
rows="4"
|
||||||
@keydown.enter.ctrl="handleTextInputConfirm"
|
@keydown.enter.ctrl="handleTextInputConfirm"
|
||||||
@keydown.esc="handleTextInputCancel"
|
@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">
|
<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>
|
<el-icon class="btn-icon"><Grid /></el-icon>
|
||||||
</button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -91,7 +95,7 @@ import { GiminiServer } from '@deotaland/utils';
|
||||||
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
// import anTypeImg from '@/assets/sketches/dwww2.png';
|
||||||
// import cz2 from '@/assets/material/cz2.png';
|
// import cz2 from '@/assets/material/cz2.png';
|
||||||
// 引入Element Plus图标库和组件
|
// 引入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'
|
import { ElIcon,ElMessage,ElSkeleton,ElImage } from 'element-plus'
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const formData = ref({
|
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 handleGenerateImage = async () => {
|
||||||
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
const iscjt = props?.cardData?.diyPromptText&&props?.cardData?.diyPromptText?.indexOf('[CJT_DEOTA]')!=-1;
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modelModal: {
|
modelModal: {
|
||||||
customizeToHome: '定制到家'
|
customizeToHome: '定制到家',
|
||||||
|
textInputPlaceholder: '请输入调整内容,例如:更改角色表情'
|
||||||
},
|
},
|
||||||
modelCard: {
|
modelCard: {
|
||||||
generateModelButton: '生成模型',
|
generateModelButton: '生成模型',
|
||||||
|
|
@ -1446,7 +1447,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modelModal: {
|
modelModal: {
|
||||||
customizeToHome: 'Customize to Home'
|
customizeToHome: 'Customize to Home',
|
||||||
|
textInputPlaceholder: 'Please enter adjustment content, e.g. change character expression'
|
||||||
},
|
},
|
||||||
modelCard: {
|
modelCard: {
|
||||||
generateModelButton: 'Generate Model',
|
generateModelButton: 'Generate Model',
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,8 @@
|
||||||
class="delete-button"
|
class="delete-button"
|
||||||
@click.stop="handleDeleteCard(index)"
|
@click.stop="handleDeleteCard(index)"
|
||||||
@mousedown.stop
|
@mousedown.stop
|
||||||
|
@touchstart.stop
|
||||||
|
@touchend.stop
|
||||||
title="删除卡片"
|
title="删除卡片"
|
||||||
>
|
>
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
|
@ -65,6 +67,7 @@
|
||||||
<!-- 根据卡片类型显示不同组件 -->
|
<!-- 根据卡片类型显示不同组件 -->
|
||||||
<IPCard
|
<IPCard
|
||||||
:combinedPromptJson="combinedPromptJson"
|
:combinedPromptJson="combinedPromptJson"
|
||||||
|
@handlePartialEdit="(imageUrl) => handlePartialEdit(imageUrl, index)"
|
||||||
@customize-to-home="handleCustomizeToHome(index)"
|
@customize-to-home="handleCustomizeToHome(index)"
|
||||||
@preview-image="handlePreviewImage"
|
@preview-image="handlePreviewImage"
|
||||||
@generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)"
|
@generate-smooth-white-model="(imageUrl)=>handleGenerateSmoothWhiteModel(index,imageUrl)"
|
||||||
|
|
@ -120,7 +123,11 @@
|
||||||
:initialIndex="currentImageIndex"
|
:initialIndex="currentImageIndex"
|
||||||
@close="showImagePreview = false"
|
@close="showImagePreview = false"
|
||||||
/>
|
/>
|
||||||
|
<CanvasEditor
|
||||||
|
v-model:visible="canvasEditorVisible"
|
||||||
|
:image-url="canvasEditorImageUrl"
|
||||||
|
@add-prompt-card="handleCanvasSave"
|
||||||
|
/>
|
||||||
<!-- 测试侧边栏动画的按钮 -->
|
<!-- 测试侧边栏动画的按钮 -->
|
||||||
<!-- <button
|
<!-- <button
|
||||||
class="test-animation-btn"
|
class="test-animation-btn"
|
||||||
|
|
@ -154,12 +161,13 @@ import HeaderComponent from '../../components/HeaderComponent/HeaderComponent.vu
|
||||||
import GuideModal from '../../components/GuideModal/index.vue';
|
import GuideModal from '../../components/GuideModal/index.vue';
|
||||||
import ImagePreviewModal from '../../components/ImagePreviewModal/index.vue';
|
import ImagePreviewModal from '../../components/ImagePreviewModal/index.vue';
|
||||||
import {useRoute,useRouter} from 'vue-router';
|
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 OrderProcessModal from '../../components/OrderProcessModal/index.vue';
|
||||||
import PurchaseModal from '../../components/PurchaseModal/index.vue';
|
import PurchaseModal from '../../components/PurchaseModal/index.vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import {Project} from './index';
|
import {Project} from './index';
|
||||||
import {ModernHome} from '../ModernHome/index.js'
|
import {ModernHome} from '../ModernHome/index.js'
|
||||||
|
const fileServer = new FileServer();
|
||||||
const modernHome = new ModernHome();
|
const modernHome = new ModernHome();
|
||||||
const Limits = ref({
|
const Limits = ref({
|
||||||
generateCount: 0,
|
generateCount: 0,
|
||||||
|
|
@ -173,6 +181,8 @@ const selectedModel = ref(null);
|
||||||
const showImportModal = ref(false);
|
const showImportModal = ref(false);
|
||||||
const importUrl = ref('https://xiaozhi.me/console/agents');
|
const importUrl = ref('https://xiaozhi.me/console/agents');
|
||||||
const showGuideModal = ref(false);
|
const showGuideModal = ref(false);
|
||||||
|
const canvasEditorVisible = ref(false);//画布编辑弹窗是否可见
|
||||||
|
const canvasEditorImageUrl = ref('');//画布编辑弹窗图片url
|
||||||
// 图片预览弹窗相关状态
|
// 图片预览弹窗相关状态
|
||||||
const showImagePreview = ref(false);
|
const showImagePreview = ref(false);
|
||||||
const previewImages = ref([]);
|
const previewImages = ref([]);
|
||||||
|
|
@ -191,6 +201,24 @@ const getGenerateCount = async ()=>{
|
||||||
// Limits.value.generateCount = data[0].model_count;
|
// Limits.value.generateCount = data[0].model_count;
|
||||||
// Limits.value.modelCount = data[1].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([
|
const cards = ref([
|
||||||
|
|
||||||
|
|
@ -429,12 +457,9 @@ const createSmartCard = (cardConfig,index=null) => {
|
||||||
// 获取当前最高层级的卡片
|
// 获取当前最高层级的卡片
|
||||||
// const highestCard;
|
// const highestCard;
|
||||||
// 获取当前最高层级的卡片(按 zIndex 最大值)
|
// 获取当前最高层级的卡片(按 zIndex 最大值)
|
||||||
let highestCard;
|
let highestCard = cards.value.reduce((prev, current) =>
|
||||||
if(index){
|
prev.zIndex > current.zIndex ? prev : current, {});
|
||||||
highestCard = cards.value[index]
|
|
||||||
}else{
|
|
||||||
highestCard = cards.value[cards.value.length-1]
|
|
||||||
}
|
|
||||||
// 计算位置在最高层级卡片右侧
|
// 计算位置在最高层级卡片右侧
|
||||||
const position = highestCard ?
|
const position = highestCard ?
|
||||||
positionCalculator.calculateRightSidePosition(highestCard, scale.value) :
|
positionCalculator.calculateRightSidePosition(highestCard, scale.value) :
|
||||||
|
|
@ -513,10 +538,8 @@ const handleCreatePromptCard = (index,params) => {
|
||||||
type:'image',
|
type:'image',
|
||||||
// inspirationImage:cardData.inspirationImage||'',
|
// inspirationImage:cardData.inspirationImage||'',
|
||||||
},index);
|
},index);
|
||||||
console.log(newCard,'newCardnewCard');
|
|
||||||
// 添加到卡片数组
|
// 添加到卡片数组
|
||||||
cards.value.push(newCard);
|
cards.value.push(newCard);
|
||||||
console.log(cards.value);
|
|
||||||
}
|
}
|
||||||
// 处理图片生成请求
|
// 处理图片生成请求
|
||||||
const handleGenerateRequested = async (params) => {
|
const handleGenerateRequested = async (params) => {
|
||||||
|
|
@ -1358,10 +1381,12 @@ html.dark .draggable-element:hover {
|
||||||
/* 删除按钮样式 */
|
/* 删除按钮样式 */
|
||||||
.delete-button {
|
.delete-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -10px;
|
top: -12px;
|
||||||
right: -10px;
|
right: -12px;
|
||||||
width: 32px;
|
width: 36px;
|
||||||
height: 32px;
|
height: 36px;
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(135deg, #6B46C1 0%, #553C9A 50%, #44337A 100%);
|
background: linear-gradient(135deg, #6B46C1 0%, #553C9A 50%, #44337A 100%);
|
||||||
|
|
@ -1382,6 +1407,8 @@ html.dark .draggable-element:hover {
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.delete-button::before {
|
.delete-button::before {
|
||||||
|
|
@ -1534,5 +1561,27 @@ p {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 0;
|
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>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue