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

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

4
app/components/index/WaterfallImageCard.vue

@ -96,9 +96,9 @@ function onImgError() {
max-width: calc(100% - 16px);
background: var(--color-surface-dark);
color: var(--color-on-dark);
font-size: 10px;
font-size: 12px;
font-weight: 500;
padding: 4px 8px;
padding: 5px 10px;
border-radius: 5px;
letter-spacing: 0.03em;
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);
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-line-clamp: 2;
-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);
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
</style>

6
app/components/index/WaterfallProjectCard.vue

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

2
app/components/index/WaterfallTextCard.vue

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

74
app/pages/collect/index.vue

@ -347,11 +347,12 @@ async function onCatFormSubmit(data: {
const showDetailModal = ref(false)
const detailCard = ref<CardDetail | null>(null)
const detailSize = ref<'default' | 'expanded'>('default')
const showCardFormModal = ref(false)
const cardFormMode = ref<'create' | 'edit'>('create')
const cardFormEditData = ref<CardDetail | null>(null)
function onCardClick(card: CardItem) {
function onCardExpand(card: CardItem) {
detailCard.value = {
id: card.id,
type: card.type,
@ -364,6 +365,7 @@ function onCardClick(card: CardItem) {
categoryId: card.categoryId,
createdAt: card.createdAt,
}
detailSize.value = 'expanded'
showDetailModal.value = true
}
@ -418,6 +420,40 @@ function onCardDragStart(e: DragEvent, item: CardItem) {
e.dataTransfer.effectAllowed = 'move'
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) {
@ -456,9 +492,9 @@ function containerWidth(): number {
}
function getColumnWidth(): number {
const padding = 48
const padding = 32
const w = containerWidth() - padding
const gap = 12
const gap = 8
return (w - gap * (columnCount.value - 1)) / columnCount.value
}
@ -493,9 +529,8 @@ function distributeAll() {
function decideColumnCount(): number {
if (isMobile.value) return 1
const w = containerWidth()
if (w < 900) return 2
if (w < 1200) return 3
return 4
if (w < 1100) return 2
return 3
}
let resizeTimer: ReturnType<typeof setTimeout> | null = null
@ -770,8 +805,8 @@ onUnmounted(() => {
class="card-reveal"
:style="{ '--enter-delay': `${ci * 80 + ri * 60}ms` }"
draggable="true"
@click="onCardClick(item)"
@dragstart="onCardDragStart($event, item)"
@dragend="onCardDragEnd($event)"
>
<!-- Hover actions -->
<div class="card-actions">
@ -787,6 +822,14 @@ onUnmounted(() => {
<button
type="button"
class="card-action-btn"
title="展开"
@click.stop="onCardExpand(item)"
>
<Icon name="lucide:maximize-2" />
</button>
<button
type="button"
class="card-action-btn"
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 })"
>
@ -895,7 +938,8 @@ onUnmounted(() => {
:visible="showDetailModal"
:card="detailCard"
:categories="categories"
@update:visible="(v) => showDetailModal = v"
:size="detailSize"
@update:visible="(v) => { showDetailModal = v; if (!v) detailSize = 'default' }"
@favorite-toggled="onDetailFavoriteToggled"
/>
@ -939,7 +983,7 @@ onUnmounted(() => {
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 280px 1fr;
grid-template-columns: 240px 1fr;
align-items: start;
gap: 0;
}
@ -953,7 +997,7 @@ onUnmounted(() => {
.home-main {
min-width: 0;
padding: 16px 24px 48px;
padding: 12px 16px 40px;
display: flex;
flex-direction: column;
}
@ -1090,7 +1134,7 @@ onUnmounted(() => {
.masonry {
display: flex;
gap: 12px;
gap: 8px;
align-items: flex-start;
}
@ -1099,7 +1143,7 @@ onUnmounted(() => {
min-width: 0;
display: flex;
flex-direction: column;
gap: 12px;
gap: 8px;
}
.card-reveal {
@ -1107,6 +1151,12 @@ onUnmounted(() => {
animation-delay: var(--enter-delay, 0ms);
cursor: pointer;
position: relative;
transition: opacity 0.2s ease;
}
.card-reveal.dragging {
opacity: 0.3;
pointer-events: none;
}
.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.gstatic.com', crossorigin: '' },
{ rel: 'stylesheet', href: 'https://fonts.googleapis.com/css2?family=Abril+Fatface&display=swap' },
],
]
},
},
css: ['~/assets/css/main.css'],

BIN
packages/drizzle-pkg/db.sqlite

Binary file not shown.
Loading…
Cancel
Save