Browse Source
- Introduced defaultCardType field in categories schema and updated migrations. - Removed slug validation from category creation and update schemas. - Updated category service to handle defaultCardType in create and update operations. - Adjusted API endpoints to reflect changes in category schema.aaaaa
17 changed files with 2876 additions and 81 deletions
@ -0,0 +1,207 @@ |
|||||
|
<script setup lang="ts"> |
||||
|
import { computed } from 'vue' |
||||
|
import { marked } from 'marked' |
||||
|
|
||||
|
const props = defineProps<{ |
||||
|
title: string |
||||
|
description: string |
||||
|
content: string |
||||
|
tags: string[] |
||||
|
aspectRatio: number |
||||
|
categoryName?: string |
||||
|
}>() |
||||
|
|
||||
|
const contentHtml = computed(() => { |
||||
|
if (!props.content) return '' |
||||
|
return marked(props.content, { breaks: true }) as string |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<template> |
||||
|
<div class="card"> |
||||
|
<span v-if="categoryName" class="card-cat-chip">{{ categoryName }}</span> |
||||
|
|
||||
|
<!-- 上部分:正文内容预览 --> |
||||
|
<div class="card-preview"> |
||||
|
<div class="preview-text" v-html="contentHtml" /> |
||||
|
<div class="content-fade" /> |
||||
|
</div> |
||||
|
|
||||
|
<!-- 下部分:标签 / 标题 / 描述 --> |
||||
|
<div class="card-footer"> |
||||
|
<div v-if="tags && tags.length" class="tags"> |
||||
|
<span v-for="tag in tags" :key="tag" class="tag">{{ tag }}</span> |
||||
|
</div> |
||||
|
<h3>{{ title }}</h3> |
||||
|
<p v-if="description" class="desc">{{ description }}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
|
<style scoped> |
||||
|
.card-cat-chip { |
||||
|
position: absolute; |
||||
|
top: 8px; |
||||
|
left: 8px; |
||||
|
z-index: 2; |
||||
|
font-size: 10px; |
||||
|
font-weight: 600; |
||||
|
padding: 2px 8px; |
||||
|
border-radius: 9999px; |
||||
|
background: rgba(204, 120, 92, 0.12); |
||||
|
color: var(--color-primary); |
||||
|
letter-spacing: 0.3px; |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
position: relative; |
||||
|
border-radius: 12px; |
||||
|
background: var(--color-surface-card); |
||||
|
overflow: hidden; |
||||
|
border: 1px solid var(--color-hairline-soft); |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
transition: transform 0.4s var(--ease-out-expo), box-shadow 0.4s var(--ease-out-expo); |
||||
|
} |
||||
|
|
||||
|
.card:hover { |
||||
|
transform: translateY(-2px) scale(1.005); |
||||
|
box-shadow: 0 8px 24px rgba(20, 20, 19, 0.08); |
||||
|
} |
||||
|
|
||||
|
/* ── 上部分:正文预览 ── */ |
||||
|
|
||||
|
.card-preview { |
||||
|
position: relative; |
||||
|
padding: 20px 20px 16px; |
||||
|
} |
||||
|
|
||||
|
.preview-text { |
||||
|
font-size: 13px; |
||||
|
font-weight: 400; |
||||
|
line-height: 1.7; |
||||
|
color: var(--color-body); |
||||
|
max-height: 96px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
/* markdown 元素降级样式(预览态,不让标题/代码过于抢眼) */ |
||||
|
.preview-text :deep(h1), |
||||
|
.preview-text :deep(h2), |
||||
|
.preview-text :deep(h3) { |
||||
|
font-size: 14px; |
||||
|
font-weight: 600; |
||||
|
line-height: 1.5; |
||||
|
color: var(--color-ink); |
||||
|
margin: 0 0 4px; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(p) { |
||||
|
margin: 0 0 4px; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(ul), |
||||
|
.preview-text :deep(ol) { |
||||
|
margin: 0 0 4px; |
||||
|
padding-left: 18px; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(li) { |
||||
|
margin-bottom: 2px; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(code) { |
||||
|
font-size: 12px; |
||||
|
background: var(--color-surface-cream-strong); |
||||
|
padding: 1px 5px; |
||||
|
border-radius: 4px; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(pre) { |
||||
|
font-size: 12px; |
||||
|
background: var(--color-surface-cream-strong); |
||||
|
padding: 8px 12px; |
||||
|
border-radius: 6px; |
||||
|
margin: 0 0 6px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(blockquote) { |
||||
|
margin: 0 0 4px; |
||||
|
padding-left: 12px; |
||||
|
border-left: 2px solid var(--color-hairline); |
||||
|
color: var(--color-muted); |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(a) { |
||||
|
color: var(--color-primary); |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
.preview-text :deep(img) { |
||||
|
display: none; |
||||
|
} |
||||
|
|
||||
|
.content-fade { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
height: 52px; |
||||
|
background: linear-gradient( |
||||
|
to bottom, |
||||
|
transparent 0%, |
||||
|
var(--color-surface-card) 100% |
||||
|
); |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
/* ── 下部分:标签 / 标题 / 描述 ── */ |
||||
|
|
||||
|
.card-footer { |
||||
|
padding: 0 20px 20px; |
||||
|
} |
||||
|
|
||||
|
.tags { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 6px; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.tag { |
||||
|
font-size: 11px; |
||||
|
font-weight: 500; |
||||
|
color: var(--color-primary); |
||||
|
background: rgba(204, 120, 92, 0.08); |
||||
|
padding: 3px 10px; |
||||
|
border-radius: 9999px; |
||||
|
line-height: 1.3; |
||||
|
} |
||||
|
|
||||
|
.card-footer h3 { |
||||
|
font-family: var(--font-display); |
||||
|
font-size: 15.5px; |
||||
|
font-weight: 400; |
||||
|
line-height: 1.3; |
||||
|
color: var(--color-ink); |
||||
|
margin: 0 0 6px; |
||||
|
letter-spacing: -0.01em; |
||||
|
overflow: hidden; |
||||
|
display: -webkit-box; |
||||
|
-webkit-line-clamp: 2; |
||||
|
-webkit-box-orient: vertical; |
||||
|
} |
||||
|
|
||||
|
.desc { |
||||
|
font-size: 12px; |
||||
|
font-weight: 400; |
||||
|
line-height: 1.55; |
||||
|
color: var(--color-muted); |
||||
|
margin: 0; |
||||
|
overflow: hidden; |
||||
|
display: -webkit-box; |
||||
|
-webkit-line-clamp: 2; |
||||
|
-webkit-box-orient: vertical; |
||||
|
} |
||||
|
</style> |
||||
Binary file not shown.
@ -0,0 +1 @@ |
|||||
|
ALTER TABLE `categories` ADD `default_card_type` text; |
||||
@ -0,0 +1,2 @@ |
|||||
|
DROP INDEX `idx_category_slug`;--> statement-breakpoint |
||||
|
ALTER TABLE `categories` DROP COLUMN `slug`; |
||||
File diff suppressed because it is too large
File diff suppressed because it is too large
Loading…
Reference in new issue