-
Welcome to Nuxt 3!
-
This is the index page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
继续滚动查看更多
+
—— 已经到底了 ——
-
\ No newline at end of file
+
+
+
+
diff --git a/server/api/cards.get.ts b/server/api/cards.get.ts
new file mode 100644
index 0000000..20e181a
--- /dev/null
+++ b/server/api/cards.get.ts
@@ -0,0 +1,140 @@
+type CardType = 'text' | 'image' | 'image-text' | 'portfolio' | 'project'
+
+interface CardData {
+ id: number
+ type: CardType
+ image?: string
+ images?: string[]
+ title: string
+ description?: string
+ tags?: string[]
+ aspectRatio: number
+}
+
+const images = [
+ 'https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=600&h=800&fit=crop',
+ 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=600&h=500&fit=crop',
+ 'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=600&h=700&fit=crop',
+ 'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=600&h=900&fit=crop',
+ 'https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?w=600&h=550&fit=crop',
+ 'https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=600&h=650&fit=crop',
+ 'https://images.unsplash.com/photo-1501854140801-50d01698950b?w=600&h=750&fit=crop',
+ 'https://images.unsplash.com/photo-1518173946687-a1e4e09e68b7?w=600&h=850&fit=crop',
+ 'https://images.unsplash.com/photo-1472214103451-9374bd1c798e?w=600&h=580&fit=crop',
+ 'https://images.unsplash.com/photo-1505144808419-1957a94ca61e?w=600&h=720&fit=crop',
+ 'https://images.unsplash.com/photo-1465146344425-f00d5f5c8f07?w=600&h=600&fit=crop',
+ 'https://images.unsplash.com/photo-1490730141103-6cac27aaab94?w=600&h=780&fit=crop',
+]
+
+const portfolioSets = [
+ [images[0], images[1], images[4], images[5]],
+ [images[2], images[3], images[6], images[7]],
+ [images[8], images[9], images[10], images[11]],
+ [images[1], images[3], images[8], images[10]],
+ [images[0], images[2], images[6], images[9]],
+ [images[4], images[5], images[7], images[11]],
+]
+
+const titles = [
+ '山间晨雾', '海岸余晖', '林深见鹿', '星河滚烫',
+ '秋日私语', '雪落无声', '荒漠孤烟', '江南水乡',
+ '云海翻涌', '古城旧梦', '樱花吹雪', '冰川纪行',
+]
+
+const descriptions = [
+ '清晨第一缕阳光穿透薄雾,洒在青翠的山谷间',
+ '海浪轻抚沙滩,余晖将天际染成珊瑚般的暖色调',
+ '密林深处,光影斑驳,万物有灵且美',
+ '浩瀚星空下,篝火噼啪作响,银河横亘天际',
+ '金黄落叶铺满小径,秋风携来果实成熟的芬芳',
+ '白雪覆盖大地,万物归于宁静与纯粹',
+ '大漠无垠,风沙雕刻出千年的纹路',
+ '小桥流水,烟雨朦胧中白墙黛瓦静静伫立',
+ '云层如海浪般在山巅之下翻涌奔腾',
+ '石板路上回响着千年前的足音,城墙斑驳诉说时光',
+ '粉色花瓣如雪般飘落,短暂而绚烂',
+ '万年冰层透着幽蓝的光,仿佛地球深处的呼吸',
+]
+
+const textContents = [
+ { title: '关于灵感', description: '灵感从不拜访懒人。它像一只羞怯的鸟,只在你俯首案前、笔耕不辍时,悄然落在你的肩头。那些看似神来之笔的瞬间,不过是千百次练习后的水到渠成。' },
+ { title: '时间之河', description: '时间是一条沉默的河流,我们都是河中的石头。水流磨平了棱角,却让内里的纹理愈发清晰。每一道纹路都是一个故事,每一次冲刷都是一次重生。' },
+ { title: '留白之美', description: '最好的设计往往藏在未说之处。一幅山水画的力量不在墨色,而在无墨之间的云雾与山岚。界面如此,人生亦然——空间不是空缺,是呼吸的节拍。' },
+ { title: '光与影', description: '没有阴影,光便失去了意义。创作的本质不是消除黑暗,而是让明暗相互成全。一幅好的摄影作品,阴影里藏着比亮处更多的故事。' },
+ { title: '旅途随笔', description: '旅行不是从一个地方到另一个地方,而是从一种视角切换到另一种视角。同一轮明月,在不同的经度里,会映出不同的乡愁。' },
+ { title: '匠人之心', description: '一把好刀,从锻打到淬火,每一步都不可省略。手艺人知道:没有什么捷径能绕过时间的沉淀。代码与器物,皆是时间的艺术。' },
+ { title: '草木人间', description: '每一片叶子都是一个小小的宇宙——脉络如河流,叶肉如大地。阳光穿过叶片的时候,像是神在端详自己的指纹。我们不过是这绿色剧场里的观众。' },
+ { title: '夜的独白', description: '凌晨三点,城市终于安静下来。窗外零星灯火像是失眠者彼此交换的暗号。在这万籁俱寂的时刻,思绪反而最清晰——或许黑夜才是思考者真正的白昼。' },
+]
+
+const projectTags = [
+ ['Vue', 'TypeScript', 'Nuxt'],
+ ['React', 'Next.js', 'Tailwind'],
+ ['Node.js', 'PostgreSQL', 'Docker'],
+ ['Figma', 'Design System', 'UI/UX'],
+ ['Python', 'FastAPI', 'MongoDB'],
+ ['Swift', 'SwiftUI', 'iOS'],
+ ['Go', 'gRPC', 'Kubernetes'],
+ ['Rust', 'WebAssembly', 'Canvas'],
+ ['Three.js', 'WebGL', 'GLSL'],
+ ['Svelte', 'SvelteKit', 'Prisma'],
+ ['Astro', 'MDX', 'Contentful'],
+ ['Flutter', 'Dart', 'Firebase'],
+]
+
+const typeCycle: CardType[] = [
+ 'image-text', 'text', 'image-text', 'portfolio',
+ 'image-text', 'image', 'image-text', 'project',
+ 'text', 'image-text', 'portfolio', 'image-text',
+ 'image', 'image-text', 'project', 'text',
+ 'image-text', 'portfolio', 'image-text', 'image',
+]
+
+function genItem(globalIdx: number): CardData {
+ const type = typeCycle[globalIdx % typeCycle.length]
+ const idx = globalIdx % 12
+ const base: Pick
= {
+ id: globalIdx + 1,
+ type,
+ title: titles[idx],
+ aspectRatio: 0.6 + Math.random() * 0.7,
+ }
+
+ switch (type) {
+ case 'text': {
+ const tc = textContents[globalIdx % textContents.length]
+ return { ...base, title: tc.title, description: tc.description, aspectRatio: 0.6 + Math.random() * 0.5 }
+ }
+ case 'image':
+ return { ...base, image: images[idx], description: undefined, aspectRatio: 0.65 + Math.random() * 0.55 }
+ case 'image-text':
+ return { ...base, image: images[idx], description: descriptions[idx], aspectRatio: 0.6 + Math.random() * 0.7 }
+ case 'portfolio': {
+ const set = portfolioSets[globalIdx % portfolioSets.length]
+ const ar = 0.85 + Math.random() * 0.15
+ return { ...base, images: set, description: descriptions[idx], aspectRatio: ar }
+ }
+ case 'project':
+ return {
+ ...base,
+ image: images[idx],
+ description: descriptions[idx],
+ tags: projectTags[globalIdx % projectTags.length],
+ aspectRatio: 0.65 + Math.random() * 0.45,
+ }
+ }
+}
+
+export default defineEventHandler(async (event) => {
+ const query = getQuery(event)
+ const page = Math.max(1, parseInt(String(query.page || '1')))
+ const pageSize = Math.min(30, Math.max(1, parseInt(String(query.pageSize || '12'))))
+
+ const start = (page - 1) * pageSize
+ const items = Array.from({ length: pageSize }, (_, i) => genItem(start + i))
+
+ const total = 60
+ const hasMore = start + pageSize < total
+
+ return { items, hasMore, page }
+})