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:
Poker Design Developer
2026-05-31 22:43:51 +08:00
parent 888b787d1e
commit ab7c6c4474

View File

@@ -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)
}
}