@ -347,11 +347,12 @@ async function onCatFormSubmit(data: {
const showDetailModal = ref ( false )
const showDetailModal = ref ( false )
const detailCard = ref < CardDetail | null > ( null )
const detailCard = ref < CardDetail | null > ( null )
const detailSize = ref < 'default' | 'expanded' > ( 'default' )
const showCardFormModal = ref ( false )
const showCardFormModal = ref ( false )
const cardFormMode = ref < 'create' | 'edit' > ( 'create' )
const cardFormMode = ref < 'create' | 'edit' > ( 'create' )
const cardFormEditData = ref < CardDetail | null > ( null )
const cardFormEditData = ref < CardDetail | null > ( null )
function onCardClick ( card : CardItem ) {
function onCardExpand ( card : CardItem ) {
detailCard . value = {
detailCard . value = {
id : card . id ,
id : card . id ,
type : card . type ,
type : card . type ,
@ -364,6 +365,7 @@ function onCardClick(card: CardItem) {
categoryId : card . categoryId ,
categoryId : card . categoryId ,
createdAt : card . createdAt ,
createdAt : card . createdAt ,
}
}
detailSize . value = 'expanded'
showDetailModal . value = true
showDetailModal . value = true
}
}
@ -418,6 +420,40 @@ function onCardDragStart(e: DragEvent, item: CardItem) {
e . dataTransfer . effectAllowed = 'move'
e . dataTransfer . effectAllowed = 'move'
e . dataTransfer . setData ( 'application/x-card-id' , String ( item . id ) )
e . dataTransfer . setData ( 'application/x-card-id' , String ( item . id ) )
}
}
const el = e . currentTarget as HTMLElement
/ / b u i l d a b l u r r e d g h o s t c l o n e f o r t h e d r a g i m a g e
const MAX_DRAG_WIDTH = 280
const ghost = el . cloneNode ( true ) as HTMLElement
const rect = el . getBoundingClientRect ( )
const ratio = rect . width > MAX_DRAG_WIDTH ? MAX_DRAG_WIDTH / rect . width : 1
ghost . style . position = 'fixed'
ghost . style . top = '-9999px'
ghost . style . left = '-9999px'
ghost . style . width = ( ratio < 1 ? MAX_DRAG_WIDTH : rect . width ) + 'px'
ghost . style . opacity = '0.55'
ghost . style . filter = 'blur(4px)'
ghost . style . animation = 'none'
ghost . style . pointerEvents = 'none'
document . body . appendChild ( ghost )
const offsetX = ( e . clientX - rect . left ) * ratio
const offsetY = ( e . clientY - rect . top ) * ratio
e . dataTransfer ! . setDragImage ( ghost , offsetX , offsetY )
/ / f a d e t h e o r i g i n a l c a r d a f t e r b r o w s e r s n a p s t h e d r a g i m a g e
requestAnimationFrame ( ( ) => {
el . classList . add ( 'dragging' )
/ / g h o s t c a n b e r e m o v e d — b r o w s e r h o l d s a r e f e r e n c e
ghost . remove ( )
} )
}
function onCardDragEnd ( e : DragEvent ) {
dragCardId = null
const el = e . currentTarget as HTMLElement
el . classList . remove ( 'dragging' )
}
}
async function onCardDropToCategory ( categoryId : string | null , categoryName : string ) {
async function onCardDropToCategory ( categoryId : string | null , categoryName : string ) {
@ -456,9 +492,9 @@ function containerWidth(): number {
}
}
function getColumnWidth ( ) : number {
function getColumnWidth ( ) : number {
const padding = 48
const padding = 32
const w = containerWidth ( ) - padding
const w = containerWidth ( ) - padding
const gap = 12
const gap = 8
return ( w - gap * ( columnCount . value - 1 ) ) / columnCount . value
return ( w - gap * ( columnCount . value - 1 ) ) / columnCount . value
}
}
@ -493,9 +529,8 @@ function distributeAll() {
function decideColumnCount ( ) : number {
function decideColumnCount ( ) : number {
if ( isMobile . value ) return 1
if ( isMobile . value ) return 1
const w = containerWidth ( )
const w = containerWidth ( )
if ( w < 900 ) return 2
if ( w < 1100 ) return 2
if ( w < 1200 ) return 3
return 3
return 4
}
}
let resizeTimer : ReturnType < typeof setTimeout > | null = null
let resizeTimer : ReturnType < typeof setTimeout > | null = null
@ -770,8 +805,8 @@ onUnmounted(() => {
class = "card-reveal"
class = "card-reveal"
: style = "{ '--enter-delay': `${ci * 80 + ri * 60}ms` }"
: style = "{ '--enter-delay': `${ci * 80 + ri * 60}ms` }"
draggable = "true"
draggable = "true"
@ click = "onCardClick(item)"
@ dragstart = "onCardDragStart($event, item)"
@ dragstart = "onCardDragStart($event, item)"
@ dragend = "onCardDragEnd($event)"
>
>
<!-- Hover actions -- >
<!-- Hover actions -- >
< div class = "card-actions" >
< div class = "card-actions" >
@ -787,6 +822,14 @@ onUnmounted(() => {
< button
< button
type = "button"
type = "button"
class = "card-action-btn"
class = "card-action-btn"
title = "展开"
@ click . stop = "onCardExpand(item)"
>
< Icon name = "lucide:maximize-2" / >
< / button >
< button
type = "button"
class = "card-action-btn"
title = "编辑"
title = "编辑"
@ click . stop = "onCardEdit({ id: item.id, type: item.type, title: item.title, description: item.description, image: item.image, images: item.images, tags: item.tags, aspectRatio: item.aspectRatio, categoryId: item.categoryId, createdAt: item.createdAt })"
@ click . stop = "onCardEdit({ id: item.id, type: item.type, title: item.title, description: item.description, image: item.image, images: item.images, tags: item.tags, aspectRatio: item.aspectRatio, categoryId: item.categoryId, createdAt: item.createdAt })"
>
>
@ -895,7 +938,8 @@ onUnmounted(() => {
: visible = "showDetailModal"
: visible = "showDetailModal"
: card = "detailCard"
: card = "detailCard"
: categories = "categories"
: categories = "categories"
@ update : visible = "(v) => showDetailModal = v"
: size = "detailSize"
@ update : visible = "(v) => { showDetailModal = v; if (!v) detailSize = 'default' }"
@ favorite - toggled = "onDetailFavoriteToggled"
@ favorite - toggled = "onDetailFavoriteToggled"
/ >
/ >
@ -939,7 +983,7 @@ onUnmounted(() => {
flex : 1 ;
flex : 1 ;
min - height : 0 ;
min - height : 0 ;
display : grid ;
display : grid ;
grid - template - columns : 28 0 px 1 fr ;
grid - template - columns : 24 0 px 1 fr ;
align - items : start ;
align - items : start ;
gap : 0 ;
gap : 0 ;
}
}
@ -953,7 +997,7 @@ onUnmounted(() => {
. home - main {
. home - main {
min - width : 0 ;
min - width : 0 ;
padding : 16 px 24 px 48 px ;
padding : 12 px 16 px 40 px ;
display : flex ;
display : flex ;
flex - direction : column ;
flex - direction : column ;
}
}
@ -1090,7 +1134,7 @@ onUnmounted(() => {
. masonry {
. masonry {
display : flex ;
display : flex ;
gap : 12 px ;
gap : 8 px ;
align - items : flex - start ;
align - items : flex - start ;
}
}
@ -1099,7 +1143,7 @@ onUnmounted(() => {
min - width : 0 ;
min - width : 0 ;
display : flex ;
display : flex ;
flex - direction : column ;
flex - direction : column ;
gap : 12 px ;
gap : 8 px ;
}
}
. card - reveal {
. card - reveal {
@ -1107,6 +1151,12 @@ onUnmounted(() => {
animation - delay : var ( -- enter - delay , 0 ms ) ;
animation - delay : var ( -- enter - delay , 0 ms ) ;
cursor : pointer ;
cursor : pointer ;
position : relative ;
position : relative ;
transition : opacity 0.2 s ease ;
}
. card - reveal . dragging {
opacity : 0.3 ;
pointer - events : none ;
}
}
. card - actions {
. card - actions {