Add interactive layer management to Editor
- Click layer to toggle visibility and redraw canvas - Click ▲/▼ buttons to reorder layers (z-index) - Active layer highlighted with left red border - Each layer now actually renders: bg/white, border/frame, pattern/suit symbol, text/corner labels - Fix fabric.js imports (Canvas, FabricText, Rect)
This commit is contained in:
18
frontend/node_modules/.vite/deps/_metadata.json
generated
vendored
18
frontend/node_modules/.vite/deps/_metadata.json
generated
vendored
@@ -1,43 +1,43 @@
|
||||
{
|
||||
"hash": "8c4b68ba",
|
||||
"configHash": "34f6f529",
|
||||
"hash": "76c4bd08",
|
||||
"configHash": "3c200fce",
|
||||
"lockfileHash": "99e89a35",
|
||||
"browserHash": "6a02d3bf",
|
||||
"browserHash": "df8b7ff8",
|
||||
"optimized": {
|
||||
"axios": {
|
||||
"src": "../../axios/index.js",
|
||||
"file": "axios.js",
|
||||
"fileHash": "9022993e",
|
||||
"fileHash": "a369977c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"element-plus": {
|
||||
"src": "../../element-plus/es/index.mjs",
|
||||
"file": "element-plus.js",
|
||||
"fileHash": "ed152bc7",
|
||||
"fileHash": "0a2f9603",
|
||||
"needsInterop": false
|
||||
},
|
||||
"fabric": {
|
||||
"src": "../../fabric/dist/index.min.mjs",
|
||||
"file": "fabric.js",
|
||||
"fileHash": "14f1f947",
|
||||
"fileHash": "a1369625",
|
||||
"needsInterop": false
|
||||
},
|
||||
"pinia": {
|
||||
"src": "../../pinia/dist/pinia.mjs",
|
||||
"file": "pinia.js",
|
||||
"fileHash": "ecda6472",
|
||||
"fileHash": "20bc890b",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vue-router": {
|
||||
"src": "../../vue-router/dist/vue-router.mjs",
|
||||
"file": "vue-router.js",
|
||||
"fileHash": "4a9d54f8",
|
||||
"fileHash": "017f450e",
|
||||
"needsInterop": false
|
||||
},
|
||||
"vue": {
|
||||
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "4a46bbb6",
|
||||
"fileHash": "70ae02ae",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
|
||||
@@ -23,9 +23,17 @@
|
||||
</div>
|
||||
|
||||
<h3 style="margin: 0 0 15px 0; font-size: 14px; color: #888;">图层管理</h3>
|
||||
<div v-for="l in layers" :key="l.id" style="padding: 10px; margin-bottom: 5px; background: #16213e; border-radius: 4px; display: flex; align-items: center; gap: 10px; cursor: pointer;">
|
||||
<span>{{ l.visible ? '👁' : '—' }}</span>
|
||||
<span style="font-size: 13px;">{{ l.name }}</span>
|
||||
<div v-for="l in layers" :key="l.id"
|
||||
@click="toggleLayer(l)"
|
||||
:style="{ padding: '10px 12px', marginBottom: '5px', background: activeLayer === l.id ? '#16213e' : '#0f3460', borderRadius: '4px', display: 'flex', alignItems: 'center', gap: '10px', cursor: 'pointer', borderLeft: activeLayer === l.id ? '3px solid #e94560' : '3px solid transparent', transition: 'all 0.2s' }">
|
||||
<span style="width: 20px; text-align: center;">{{ l.visible ? '👁' : '—' }}</span>
|
||||
<span style="font-size: 13px; flex: 1;">{{ l.name }}</span>
|
||||
<span style="display: flex; flex-direction: column; gap: 2px;">
|
||||
<button @click.stop="moveLayer(l, -1)" :disabled="l.zIndex <= 0"
|
||||
:style="{ border: 'none', background: 'transparent', color: l.zIndex <= 0 ? '#333' : '#888', cursor: l.zIndex <= 0 ? 'default' : 'pointer', fontSize: '10px', padding: '0' }">▲</button>
|
||||
<button @click.stop="moveLayer(l, 1)" :disabled="l.zIndex >= layers.length - 1"
|
||||
: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>
|
||||
</aside>
|
||||
|
||||
@@ -49,7 +57,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted, computed, nextTick } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { Canvas, FabricText } from 'fabric'
|
||||
import { Canvas, FabricText, Rect } from 'fabric'
|
||||
import axios from 'axios'
|
||||
|
||||
const route = useRoute()
|
||||
@@ -60,6 +68,7 @@ const pname = ref('')
|
||||
const currentSuit = ref('spade')
|
||||
const currentCard = ref('spade-A')
|
||||
const fabricCanvas = ref(null)
|
||||
const activeLayer = ref('bg')
|
||||
const projectId = computed(() => route.params.projectId)
|
||||
|
||||
const suits = ['spade', 'heart', 'club', 'diamond']
|
||||
@@ -76,12 +85,12 @@ const currentCards = computed(() => {
|
||||
return cards
|
||||
})
|
||||
|
||||
const layers = [
|
||||
{ id: 'bg', name: '背景层', visible: true },
|
||||
{ id: 'border', name: '边框层', visible: true },
|
||||
{ id: 'pattern', name: '图案层', visible: true },
|
||||
{ id: 'text', name: '文字层', visible: true }
|
||||
]
|
||||
const layers = ref([
|
||||
{ id: 'bg', name: '背景层', visible: true, zIndex: 0 },
|
||||
{ id: 'border', name: '边框层', visible: true, zIndex: 1 },
|
||||
{ id: 'pattern', name: '图案层', visible: true, zIndex: 2 },
|
||||
{ id: 'text', name: '文字层', visible: true, zIndex: 3 }
|
||||
])
|
||||
|
||||
function getSymbol(suit) {
|
||||
return { spade: '♠', heart: '♥', club: '♣', diamond: '♦' }[suit] || ''
|
||||
@@ -91,6 +100,23 @@ function isRed(suit) {
|
||||
return suit === 'heart' || suit === 'diamond'
|
||||
}
|
||||
|
||||
function toggleLayer(layer) {
|
||||
activeLayer.value = layer.id
|
||||
layer.visible = !layer.visible
|
||||
drawCard()
|
||||
}
|
||||
|
||||
function moveLayer(layer, delta) {
|
||||
const newZ = layer.zIndex + delta
|
||||
const swapped = layers.value.find(l => l.zIndex === newZ)
|
||||
if (swapped) {
|
||||
swapped.zIndex = layer.zIndex
|
||||
layer.zIndex = newZ
|
||||
layers.value.sort((a, b) => a.zIndex - b.zIndex)
|
||||
drawCard()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
if (projectId.value) {
|
||||
try {
|
||||
@@ -109,7 +135,7 @@ function initCanvas() {
|
||||
fabricCanvas.value = new Canvas('main-canvas', {
|
||||
width: 400,
|
||||
height: 560,
|
||||
backgroundColor: '#ffffff'
|
||||
backgroundColor: '#f5f5f5'
|
||||
})
|
||||
drawCard()
|
||||
}
|
||||
@@ -119,22 +145,47 @@ function drawCard() {
|
||||
if (!c) return
|
||||
|
||||
c.clear()
|
||||
c.backgroundColor = '#ffffff'
|
||||
|
||||
const sortedLayers = [...layers.value].sort((a, b) => a.zIndex - b.zIndex)
|
||||
const suit = currentSuit.value
|
||||
const rank = currentCard.value.split('-')[1] || 'A'
|
||||
const sym = getSymbol(suit)
|
||||
const color = isRed(suit) ? '#FF0000' : '#000000'
|
||||
const cardW = 400
|
||||
const cardH = 560
|
||||
|
||||
const topText = new fabric.FabricText(`${rank}${sym}`, {
|
||||
left: 30, top: 25, fontSize: 36, fill: color, selectable: true
|
||||
})
|
||||
c.add(topText)
|
||||
for (const layer of sortedLayers) {
|
||||
if (!layer.visible) continue
|
||||
|
||||
const center = new fabric.FabricText(sym, {
|
||||
left: 200, top: 280, fontSize: 80, fill: color, selectable: true, originX: 'center', originY: 'center'
|
||||
})
|
||||
c.add(center)
|
||||
if (layer.id === 'bg') {
|
||||
const bg = new Rect({ left: 0, top: 0, width: cardW, height: cardH, fill: '#ffffff', selectable: false })
|
||||
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 })
|
||||
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'
|
||||
})
|
||||
c.add(center)
|
||||
}
|
||||
|
||||
if (layer.id === 'text') {
|
||||
const topLabel = new FabricText(`${rank}${sym}`, {
|
||||
left: 18, top: 16, fontSize: 32, fill: color, selectable: true
|
||||
})
|
||||
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'
|
||||
})
|
||||
c.add(bottomLabel)
|
||||
}
|
||||
}
|
||||
|
||||
c.renderAll()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user