Browse Source

feat: update line clamping and padding in various WaterfallCard components for improved text display

- Increased line clamping in WaterfallCard.vue, WaterfallImageCard.vue, WaterfallPortfolioCard.vue, WaterfallProjectCard.vue, and WaterfallTextCard.vue to enhance text visibility.
- Adjusted padding in WaterfallProjectCard.vue and WaterfallImageCard.vue for better layout.
- Enhanced drag-and-drop functionality in collect/index.vue with a blurred ghost clone effect during drag.
- Modified column width and gap settings for responsive design in collect/index.vue.
- Updated detail modal handling to support expanded view in collect/index.vue.
- General CSS adjustments for improved spacing and layout consistency across components.
as
npmrun 1 week ago
parent
commit
6c66293ee1
  1. 53
      app/components/index/CardDetailModal.vue
  2. 1031
      app/components/index/CardFormModal.vue
  3. 6
      app/components/index/WaterfallCard.vue
  4. 4
      app/components/index/WaterfallImageCard.vue
  5. 4
      app/components/index/WaterfallPortfolioCard.vue
  6. 6
      app/components/index/WaterfallProjectCard.vue
  7. 2
      app/components/index/WaterfallTextCard.vue
  8. 74
      app/pages/collect/index.vue
  9. 3
      nuxt.config.ts
  10. BIN
      packages/drizzle-pkg/db.sqlite

53
app/components/index/CardDetailModal.vue

@ -17,11 +17,21 @@ export interface CardDetail {
articles?: { id: number; title: string }[] articles?: { id: number; title: string }[]
} }
const props = defineProps<{ const props = withDefaults(defineProps<{
card: CardDetail | null card: CardDetail | null
visible: boolean visible: boolean
categories: CategoryNode[] categories: CategoryNode[]
}>() size?: 'default' | 'expanded'
}>(), {
size: 'default',
})
const dialogStyle = computed(() => {
if (props.size === 'expanded') {
return { width: '80vw' }
}
return { width: '720px' }
})
const emit = defineEmits<{ const emit = defineEmits<{
'update:visible': [v: boolean] 'update:visible': [v: boolean]
@ -108,10 +118,10 @@ function formatDate(d?: string) {
<BoDialog <BoDialog
:show="visible" :show="visible"
:mask-can-close="true" :mask-can-close="true"
:style="{ width: '600px' }" :style="dialogStyle"
@update:show="(v: boolean) => emit('update:visible', v)" @update:show="(v: boolean) => emit('update:visible', v)"
> >
<div v-if="card" class="detail"> <div v-if="card" class="detail" :class="{ 'detail--expanded': size === 'expanded' }">
<!-- Image area --> <!-- Image area -->
<div v-if="card.images && card.images.length > 0" class="detail-image-area"> <div v-if="card.images && card.images.length > 0" class="detail-image-area">
<img <img
@ -196,7 +206,7 @@ function formatDate(d?: string) {
.detail { .detail {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 85vh; max-height: 92vh;
background: var(--color-canvas, #faf9f5); background: var(--color-canvas, #faf9f5);
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
@ -256,8 +266,8 @@ function formatDate(d?: string) {
.detail-image-area { .detail-image-area {
position: relative; position: relative;
background: var(--color-surface-card); background: var(--color-surface-card);
min-height: 200px; min-height: 180px;
max-height: 55vh; max-height: 40vh;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -265,7 +275,7 @@ function formatDate(d?: string) {
.detail-image { .detail-image {
width: 100%; width: 100%;
max-height: 55vh; max-height: 40vh;
object-fit: cover; object-fit: cover;
display: block; display: block;
} }
@ -314,7 +324,6 @@ function formatDate(d?: string) {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 12px; gap: 12px;
overflow-y: auto;
} }
.detail-meta-row { .detail-meta-row {
@ -459,4 +468,30 @@ function formatDate(d?: string) {
.detail-article-link:hover { .detail-article-link:hover {
background: #d0ece4; background: #d0ece4;
} }
/* ── Expanded mode ── */
.detail--expanded {
height: 80vh;
max-height: none;
}
.detail--expanded .detail-image-area {
max-height: 55vh;
}
.detail--expanded .detail-image {
max-height: 55vh;
}
.detail--expanded .detail-body {
flex: 1;
overflow-y: auto;
padding: 32px 40px 40px;
}
.detail--expanded .detail-desc {
font-size: 16px;
line-height: 1.9;
}
</style> </style>

1031
app/components/index/CardFormModal.vue

File diff suppressed because it is too large

6
app/components/index/WaterfallCard.vue

@ -125,7 +125,7 @@ function onImgError() {
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
@ -138,12 +138,12 @@ function onImgError() {
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 4;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.card-text { .card-text {
padding: 0 16px 16px; padding: 0 18px 20px;
} }
.img-fallback { .img-fallback {

4
app/components/index/WaterfallImageCard.vue

@ -96,9 +96,9 @@ function onImgError() {
max-width: calc(100% - 16px); max-width: calc(100% - 16px);
background: var(--color-surface-dark); background: var(--color-surface-dark);
color: var(--color-on-dark); color: var(--color-on-dark);
font-size: 10px; font-size: 12px;
font-weight: 500; font-weight: 500;
padding: 4px 8px; padding: 5px 10px;
border-radius: 5px; border-radius: 5px;
letter-spacing: 0.03em; letter-spacing: 0.03em;
opacity: 0; opacity: 0;

4
app/components/index/WaterfallPortfolioCard.vue

@ -140,7 +140,7 @@ const totalImages = computed(() => props.images.length)
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
@ -153,7 +153,7 @@ const totalImages = computed(() => props.images.length)
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
</style> </style>

6
app/components/index/WaterfallProjectCard.vue

@ -143,7 +143,7 @@ function onImgError() {
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); text-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
@ -156,12 +156,12 @@ function onImgError() {
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 4;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
.card-body { .card-body {
padding: 0 16px 16px; padding: 0 18px 20px;
} }
.tags { .tags {

2
app/components/index/WaterfallTextCard.vue

@ -66,7 +66,7 @@ defineProps<{
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 4; -webkit-line-clamp: 8;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
</style> </style>

74
app/pages/collect/index.vue

@ -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
// build a blurred ghost clone for the drag image
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)
// fade the original card after browser snaps the drag image
requestAnimationFrame(() => {
el.classList.add('dragging')
// ghost can be removed browser holds a reference
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: 280px 1fr; grid-template-columns: 240px 1fr;
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: 16px 24px 48px; padding: 12px 16px 40px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -1090,7 +1134,7 @@ onUnmounted(() => {
.masonry { .masonry {
display: flex; display: flex;
gap: 12px; gap: 8px;
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: 12px; gap: 8px;
} }
.card-reveal { .card-reveal {
@ -1107,6 +1151,12 @@ onUnmounted(() => {
animation-delay: var(--enter-delay, 0ms); animation-delay: var(--enter-delay, 0ms);
cursor: pointer; cursor: pointer;
position: relative; position: relative;
transition: opacity 0.2s ease;
}
.card-reveal.dragging {
opacity: 0.3;
pointer-events: none;
} }
.card-actions { .card-actions {

3
nuxt.config.ts

@ -17,8 +17,9 @@ export default defineNuxtConfig({
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { rel: 'preconnect', href: 'https://fonts.googleapis.com' },
{ rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' }, { rel: 'preconnect', href: 'https://fonts.gstatic.com', crossorigin: '' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap' }, { rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap' },
], ]
}, },
}, },
css: ['~/assets/css/main.css'], css: ['~/assets/css/main.css'],

BIN
packages/drizzle-pkg/db.sqlite

Binary file not shown.
Loading…
Cancel
Save