Make all canvas objects selectable and editable
- All objects now selectable on canvas (hasControls=false, lockMovement enabled) - Clicking canvas objects syncs activeLayer - Add property editor panel for bg/border/text layers - Background: color picker to change fill color - Border: color picker + thickness slider - Text: font size slider - Layer properties (fillColor, strokeColor, textSize) stored in layer state
This commit is contained in:
@@ -35,6 +35,27 @@
|
||||
:style="{ border: 'none', background: 'transparent', color: l.zIndex >= layers.length - 1 ? '#333' : '#888', cursor: l.zIndex >= layers.length - 1 ? 'default' : 'pointer', fontSize: '10px', padding: '0' }">▼</button>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h3 style="margin: 25px 0 12px 0; font-size: 14px; color: #888;">属性编辑</h3>
|
||||
<div v-if="activeLayer === 'bg'" style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label style="font-size: 12px; color: #aaa;">背景颜色</label>
|
||||
<input v-model="bgColor" type="color" @input="updateBgColor" style="width: 100%; height: 30px; border: none; border-radius: 4px; cursor: pointer;">
|
||||
<button @click="applyBgColor" style="padding: 6px 12px; background: #e94560; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">应用背景色</button>
|
||||
</div>
|
||||
<div v-if="activeLayer === 'border'" style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label style="font-size: 12px; color: #aaa;">边框颜色</label>
|
||||
<input v-model="borderColor" type="color" @input="updateBorderColor" style="width: 100%; height: 30px; border: none; border-radius: 4px; cursor: pointer;">
|
||||
<label style="font-size: 12px; color: #aaa;">边框粗细: {{ borderWidth }}px</label>
|
||||
<input v-model="borderWidth" type="range" min="0" max="10" step="1" @input="applyBorder" style="width: 100%; cursor: pointer;">
|
||||
<button @click="applyBorder" style="padding: 6px 12px; background: #e94560; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">应用边框</button>
|
||||
</div>
|
||||
<div v-if="activeLayer === 'text'" style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<label style="font-size: 12px; color: #aaa;">字体大小: {{ textSize }}px</label>
|
||||
<input v-model="textSize" type="range" min="12" max="120" step="2" @input="applyTextStyle" style="width: 100%; cursor: pointer;">
|
||||
</div>
|
||||
<div v-if="!['bg','border','text','pattern'].includes(activeLayer)" style="color: #888; font-size: 12px; padding: 10px;">
|
||||
选中画布上的对象查看属性
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main style="flex: 1; display: flex; justify-content: center; align-items: center; background: #1a1a2e; padding: 20px;">
|
||||
@@ -86,12 +107,44 @@ const currentCards = computed(() => {
|
||||
})
|
||||
|
||||
const layers = ref([
|
||||
{ id: 'bg', name: '背景层', visible: true, zIndex: 0 },
|
||||
{ id: 'border', name: '边框层', visible: true, zIndex: 1 },
|
||||
{ id: 'bg', name: '背景层', visible: true, zIndex: 0, fillColor: '#ffffff' },
|
||||
{ id: 'border', name: '边框层', visible: true, zIndex: 1, strokeColor: '#333333', strokeWidth: 2 },
|
||||
{ id: 'pattern', name: '图案层', visible: true, zIndex: 2 },
|
||||
{ id: 'text', name: '文字层', visible: true, zIndex: 3 }
|
||||
{ id: 'text', name: '文字层', visible: true, zIndex: 3, textSize: 32 }
|
||||
])
|
||||
|
||||
const bgColor = ref('#ffffff')
|
||||
const borderColor = ref('#333333')
|
||||
const borderWidth = ref(2)
|
||||
const textSize = ref(32)
|
||||
|
||||
function updateBgColor() {
|
||||
const l = layers.value.find(x => x.id === 'bg')
|
||||
if (l) l.fillColor = bgColor.value
|
||||
}
|
||||
|
||||
function applyBgColor() {
|
||||
updateBgColor()
|
||||
drawCard()
|
||||
}
|
||||
|
||||
function applyBorder() {
|
||||
const l = layers.value.find(x => x.id === 'border')
|
||||
if (l) { l.strokeColor = borderColor.value; l.strokeWidth = parseInt(borderWidth.value) }
|
||||
drawCard()
|
||||
}
|
||||
|
||||
function updateBorderColor() {
|
||||
const l = layers.value.find(x => x.id === 'border')
|
||||
if (l) l.strokeColor = borderColor.value
|
||||
}
|
||||
|
||||
function applyTextStyle() {
|
||||
const l = layers.value.find(x => x.id === 'text')
|
||||
if (l) l.textSize = parseInt(textSize.value)
|
||||
drawCard()
|
||||
}
|
||||
|
||||
function getSymbol(suit) {
|
||||
return { spade: '♠', heart: '♥', club: '♣', diamond: '♦' }[suit] || ''
|
||||
}
|
||||
@@ -135,8 +188,24 @@ function initCanvas() {
|
||||
fabricCanvas.value = new Canvas('main-canvas', {
|
||||
width: 400,
|
||||
height: 560,
|
||||
backgroundColor: '#f5f5f5'
|
||||
backgroundColor: '#f5f5f5',
|
||||
selection: true
|
||||
})
|
||||
|
||||
fabricCanvas.value.on('selection:created', (e) => {
|
||||
const obj = e.selected[0]
|
||||
if (obj && obj.layerId) {
|
||||
activeLayer.value = obj.layerId
|
||||
}
|
||||
})
|
||||
|
||||
fabricCanvas.value.on('selection:updated', (e) => {
|
||||
const obj = e.selected[0]
|
||||
if (obj && obj.layerId) {
|
||||
activeLayer.value = obj.layerId
|
||||
}
|
||||
})
|
||||
|
||||
drawCard()
|
||||
}
|
||||
|
||||
@@ -158,31 +227,37 @@ function drawCard() {
|
||||
if (!layer.visible) continue
|
||||
|
||||
if (layer.id === 'bg') {
|
||||
const bg = new Rect({ left: 0, top: 0, width: cardW, height: cardH, fill: '#ffffff', selectable: false })
|
||||
const bg = new Rect({ left: 0, top: 0, width: cardW, height: cardH, fill: layer.fillColor || '#ffffff', selectable: true, hasControls: false, lockMovementX: true, lockMovementY: true, stroke: 'transparent', strokeWidth: 0 })
|
||||
bg.layerId = 'bg'
|
||||
c.add(bg)
|
||||
}
|
||||
|
||||
if (layer.id === 'border') {
|
||||
const frame = new Rect({ left: 10, top: 10, width: cardW - 20, height: cardH - 20, fill: 'transparent', stroke: '#333', strokeWidth: 2, selectable: false })
|
||||
const frame = new Rect({ left: 10, top: 10, width: cardW - 20, height: cardH - 20, fill: 'transparent', stroke: layer.strokeColor || '#333', strokeWidth: layer.strokeWidth || 2, selectable: true, hasControls: false, lockMovementX: true, lockMovementY: true })
|
||||
frame.layerId = 'border'
|
||||
c.add(frame)
|
||||
}
|
||||
|
||||
if (layer.id === 'pattern') {
|
||||
const center = new FabricText(sym, {
|
||||
left: cardW / 2, top: cardH / 2, fontSize: 80, fill: color, selectable: true, originX: 'center', originY: 'center'
|
||||
left: cardW / 2, top: cardH / 2, fontSize: 80, fill: color, selectable: true, hasControls: false, lockMovementX: true, lockMovementY: true, originX: 'center', originY: 'center'
|
||||
})
|
||||
center.layerId = 'pattern'
|
||||
c.add(center)
|
||||
}
|
||||
|
||||
if (layer.id === 'text') {
|
||||
const ts = layer.textSize || 32
|
||||
const topLabel = new FabricText(`${rank}${sym}`, {
|
||||
left: 18, top: 16, fontSize: 32, fill: color, selectable: true
|
||||
left: 18, top: 16, fontSize: ts, fill: color, selectable: true, hasControls: false, lockMovementX: true, lockMovementY: true
|
||||
})
|
||||
topLabel.layerId = 'text'
|
||||
c.add(topLabel)
|
||||
|
||||
const bottomLabel = new FabricText(`${sym}${rank}`, {
|
||||
left: cardW - 18, top: cardH - 16, fontSize: 32, fill: color, selectable: true, originX: 'right', originY: 'bottom'
|
||||
left: cardW - 18, top: cardH - 16, fontSize: ts, fill: color, selectable: true, hasControls: false, lockMovementX: true, lockMovementY: true, originX: 'right', originY: 'bottom'
|
||||
})
|
||||
bottomLabel.layerId = 'text'
|
||||
c.add(bottomLabel)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user