feat(theme-yun): support collections feature, close #185

This commit is contained in:
YunYouJun 2025-07-19 21:52:33 +08:00
parent 88e4f8666e
commit 6f7c112bf2
34 changed files with 805 additions and 68 deletions

View File

@ -33,10 +33,10 @@ declare module 'vue-router/auto-routes' {
'/bangumi/': RouteRecordInfo<'/bangumi/', '/bangumi', Record<never, never>, Record<never, never>>,
'/categories/': RouteRecordInfo<'/categories/', '/categories', Record<never, never>, Record<never, never>>,
'/collections/': RouteRecordInfo<'/collections/', '/collections', Record<never, never>, Record<never, never>>,
'/collections/i-and-she/': RouteRecordInfo<'/collections/i-and-she/', '/collections/i-and-she', Record<never, never>, Record<never, never>>,
'/collections/i-and-she/1': RouteRecordInfo<'/collections/i-and-she/1', '/collections/i-and-she/1', Record<never, never>, Record<never, never>>,
'/collections/i-and-she/2': RouteRecordInfo<'/collections/i-and-she/2', '/collections/i-and-she/2', Record<never, never>, Record<never, never>>,
'/collections/i-and-she/3': RouteRecordInfo<'/collections/i-and-she/3', '/collections/i-and-she/3', Record<never, never>, Record<never, never>>,
'/collections/hamster/': RouteRecordInfo<'/collections/hamster/', '/collections/hamster', Record<never, never>, Record<never, never>>,
'/collections/hamster/1': RouteRecordInfo<'/collections/hamster/1', '/collections/hamster/1', Record<never, never>, Record<never, never>>,
'/collections/hamster/2': RouteRecordInfo<'/collections/hamster/2', '/collections/hamster/2', Record<never, never>, Record<never, never>>,
'/collections/hamster/3': RouteRecordInfo<'/collections/hamster/3', '/collections/hamster/3', Record<never, never>, Record<never, never>>,
'/collections/love-and-peace/': RouteRecordInfo<'/collections/love-and-peace/', '/collections/love-and-peace', Record<never, never>, Record<never, never>>,
'/collections/love-and-peace/1': RouteRecordInfo<'/collections/love-and-peace/1', '/collections/love-and-peace/1', Record<never, never>, Record<never, never>>,
'/collections/love-and-peace/2': RouteRecordInfo<'/collections/love-and-peace/2', '/collections/love-and-peace/2', Record<never, never>, Record<never, never>>,

View File

@ -0,0 +1,202 @@
---
title: 第一章 仓鼠的笼子
layout: collection
---
## 仓鼠的笼子
“喂(~~呐~~),你说世界上真的有外星人吗?”我一边喂着毛茸茸的仓鼠,一边眨着眼睛故作调皮的问道。
夏日的午后总会让人慵懒,好在室内却是凉意凛然。
“虽说眼见为实,不过我想,外星人是一定存在的。至少从人类的渺小程度来看,是这样。”他的视线从书本上离开了会儿,露出不置可否的笑容。
“那为什么我们到现在都不能见到他们呢?一定会有科技稍早于我们的外星人搭着如魔法般的飞船来到此地才对啊。”我追问道,并将那可爱的小仓鼠提起放在转轮上。它也像是明白了什么一样,开始哒哒哒地跑了起来。
<!--more-->
“即便是地球上也仍有许多我们未曾见过的生物啊,不过我宁愿呆在午后,看着前人的幻想,也不想去探索那些未知的…”他像是没找到合适的词汇,“动物世界?”
“外星人也会像你这般怠惰吗?”这样的回答让我不由得白了他一眼,同时双手将那正跑着的小家伙捧了起来。
“啊对了动物世界的话12:28 的时候 CCTV-6 有重播哦!”
我聚焦着视线,准备打开墙上的屏幕。“要不要看?不然这气氛可是太过安静了。”
“记得这么清楚呢,随便咯。”他轻轻翻过一页,对我莫名其妙的提议并不在意。
突如其来的嘈杂与亮光让手中那毛茸茸的东西一阵颤抖,那灰溜溜的眼睛转动着投向其来源。“你也喜欢看吗?”我顺着它的毛发摸下,轻声问道。
“仓鼠的视力只能看到两米之内的东西,而且只能分辨灰度,所以它眼中的《动物世界》应当只是一团黑白的模糊光影哦。当然听觉倒是不错,不过正因如此,会很讨厌嘈杂声呢。”他依旧盯着书,悠悠地说道。
“咳…不用你多嘴。”
“哇,连这样的情景都能拍到,到底是怎么做到的啊!”我盯着墙上的屏幕,试图转移话题。
“不觉得这更像是人类在单方面偷窥吗,也许你所期待的外星人也正这样在隐蔽地观察着我们,而我们也如它们一样毫无察觉。”
“我们与他们科技的差距就如同虫子与我们一般,所以我们才能这样安逸地抱怨着在这宇宙中的孤单啊。”他拿着书本向后仰去,座椅也随之倾斜。
不知怎么,我从他的话语中似乎听到一丝失落。
“……那我们安稳地活到现在真是谢天谢地呢,如果哪日外星人家的野孩子踏入这里岂不是糟了,皆被如同玩耍一般拿捏?”我试探性地问了问。
“这倒也许不必担心,如果发现一种新奇的生物,更多的是去划立保护区去保护,而不是将其生活区域开放为旅游景点吧。”
“说不定人类之所以能够安稳地活到现在,并得以为自己的幸运而沾沾自喜的缘由,正是如此呢。”他突然推了推眼镜,正色道。
“唔,像太阳系一样大的保护区,大手笔啊。”我想要配合着感叹道。
“若只是那样也罢,但如只是畜牧或种植般保护着,到了收割的季节会怎样呢?”不知为何,他突然对这话题严肃起来,并且滔滔不绝地说了下去。“为了看护人类,也许还会设立诸如饲养员或是观察员一样的职位吧,肥料就好比是人类科技史上那不可思议地突飞猛进的缘由,开荒,播种,施肥,生长,把人类的发展史囊括其中,然后到了丰收的季节,开始收割的盛宴。”
“很了解嘛!那么你是观察员吗?”我转过头来,笑着回问道。手中的小仓鼠像是受到了惊吓,四处东张西望起来。
他微微一笑,没有回答,继续看起书来,脸上的严肃也早已一扫而空。
只是莫名的尴尬气氛再次降临,桌上漏斗中的沙粒也伴随着时间一点一滴地消逝。甚至连那刚用完餐的小仓鼠也再次沉沉睡去。
“谢谢款待。”他将手中的书本轻轻合起并放上书架,准备告别。
……
“请等一下,明天请务必还要来,我有秘密要告诉你。”我决定诉出琢磨许久的话语。
他像是有些惊讶,但旋即点了点头。
单向的玻璃墙幕将远处夕阳的光线引入,为屋内染上暖色。如同他笑容一般的暖意荡漾而开。
“我也有。”
我按捺住扑通扑通跳着的心脏,在日历上标注好关于世界毁灭的倒计时,准备入睡。
## 关于世界毁灭的二三事
“早上好。”
“早上好。打扰了。”打过招呼的他如往常一般立在门前,言辞匮乏。
“快进来吧。”不待他下句话说出,我便催促起来。
今天的室内风格是久违的星空,无了昨日的透明简洁,繁杂的星点点缀在夜空,朦胧地将全部笼罩,嫩绿的草儿则将山坡铺满,让人犹如置身荒野。只是散落的家具让人显得像是无家可归。天空也不是单纯地靛蓝,而是在远处透着黄昏,宛如昨日的延续。
“很棒的场景啊!”他难得的发出了句称赞。
“送你好了,不过你可能要无福消受了。”我将数据打包着发送至他的邮箱。
他则从熟悉的书架处抽下昨日尚未看完的书本,对我一如既往的世界毁灭的笑话不置可否。
“不问我吗?”
“想说的话,你一定憋不住吧?” 他一副将我看穿的样子,并如往常一样打开书本读了起来。
“看完了吗?”我无聊地看着还在熟睡的小仓鼠,琢磨着自己的言辞。
“快了。”
“你好像很喜欢这本书啊。”我抱头仰面躺在松软的草地上,盯着夜空投影出的星点,说道:“你先还是我先?”
“女士优先。”他也学着我的姿势躺下。
“这时候谦让就太狡猾了,不过我倒是无所谓啦,你要做好心理准备哦。”
“从各个方面。”我补充道。
“准备好了?”
“嗯。”对于我多余的废话,他没有丝毫厌烦。而且总感觉今天的他有些不一样。
“哪怕是你的秘密可能变得无关紧要?”我继续追问道。
他的脸上闪过一丝慌乱,但还是点了点头。
“其实,…”我深吸了一口气,想要看看是否能吊起他的胃口,果然他像是有了什么猜测而紧张起来。
“距离世界毁灭还有三天。”
## 星空的狂想
他呆滞了下,又像是有点生气:“你的秘密不会就是为了开这种玩笑吧。”但当他看到我严肃的面孔时,语气反倒慢慢缓和起来。
“是真的哦。”我再次强调。
“那么有何证据吗?”他顿了顿,“突然说这种话,没什么人会信的吧,我也没有中二到那种程度。”
“说来话长啊…”
“那就慢慢说。”他撇了撇我。
“因为太长了,不知道从何说起啊。”我夸张地叹了口气,但他显然不会就此罢休。
“你认为自己是真实存在的吗?”我抛出了不明所以的问题。
“你接下来要告诉我,我不过是虚拟人物,然后这个虚拟世界因为某些原因三天后便要销毁了吗?”
“哇,猜的真准啊,虽然有些区别,但是总体意思差不多啦。”我称赞道。
“喂,别用那么简单的理由敷衍我啊。”他难得的担当了吐槽角色。“接下来的展开该不会是要说 boss 就是那只小仓鼠吧。就像某漫游指南里一样。”
“很遗憾,让你失望了,它只是仓鼠而已,由普通的单细胞生物进化而来,和你一样。”我扶额摇了摇头。
好在他的猜想让刚刚凝固的气氛再次缓和下来,我也因此得以继续开口。
“还记得我们昨日的话题吗?其实我就是观测者哦,只不过在我发现你们文明前,你们便已毁灭了。如今的世界是采集信息建模后所发展出来的,就像是考古模型之类的东西,而目的正是为了探索你们灭亡的真相,我可不是像你这样怠惰的人哦。”我顺便反击起他昨日的论断。
他眉头紧皱,大概是在思考我话语的真实性。
“还在怀疑啊,要我施展些神迹吗?”我拉起他的手臂,并挥手将远处的星空拉近,与此同时,草坪则向下退去。穿透房间虚拟景色的壁障后,城市也在下方不断缩小,云层也一晃即逝,直至那熟悉的蓝色水球出现在我们下方的视野才开始停下。
我松开他的手臂,在深邃的太空中伸起懒腰,并打量着他的惊魂未定。不待他发问,又接着说道:“如你所见,这个看似真切的世界,距离推算的毁灭日期只有三天了。很期待吧,关于一切的真相。”
只不过完全看不出要灭亡的样子呢。我低声念叨着。
“仅仅依靠这样的推算,就能将世界毁灭都模拟出来吗?包括那些不可控的变数?”他显然十分惊讶,但似乎开始有些相信我的话语了。
“无用的变数是多余的,就像你决定开车上班,你所着的服装与所遇的风景都不会轻易影响你到达公司这个事实哦。人类文明的毁灭并没有外来因素的迹象,也就是说,原因只在于你们自己。而且文明毁灭足以作为历史进程的奇点,可不会随随便便地被其他因素所改变,这是注定会发生的事情。就像我提前告知你,也不会对毁灭的进程有丝毫影响。大概这就可以理解为那玄而又玄的命运吧,属于你们人类的命运。”
“况且,你以为这个世界有多大呢?说到底啊,你们无论怎么努力连太阳系都飞不出。那么之外的真假对你们又有何用呢?”我将远处的一颗发亮的恒星拉近,把那如乒乓球大小的发光气团推至他的眼前。“喏,这就是你们在努力观察的宇宙。”
他一言不发,努力地维持着镇定。
大概我不应该说出来吧,也许如大家一样在梦境中带着对次日的幻想而消亡才是最好的选择。
“没事吧,要我消除掉这份不安的记忆吗?”我伸出手在他眼前晃了晃。
“没事,谢谢。不过为什么选择告诉我呢?”
“额…因为你很可爱啊。”我挠了挠头,有些不知所措。“像小仓鼠一样?”
“对了,你的秘密呢?”我急忙切换话题。
“啊…”他像是吓了一跳,果然很像小仓鼠啊。“那种事情已经无关紧要了。”他低下头来,但失落的表情显而易见。
“我想我该回去了。”
“那么世界末日见。”我摆摆手,不做挽留。
“谢谢款待,再见……”他丢下惯例的话语逃离而去。
## 世界的终焉
日历上的倒计时,一点点减少,只是太阳依旧如往常一般东起西落,包括那呱噪的蝉鸣。一切的迹象都显示着这世界依然安然无恙。
这是无法否认的第四天。
匆忙模样的他立在门前,反倒让我有些狼狈。少见的不待我招呼,便已迈入。只不过他的身体却开始漂浮起来,因为今日的室内是那日太空风景的延续。
“大概出了些什么问题。”我尴尬地挠了挠头。“按理说,水星、金星什么的会爆炸吧。”
不顾他的疑惑,我自顾自地说了下去。比如加速器撞出了什么不得了的东西啊,生化危机啊,世界大战啊,基因变异啊,为什么这么和平啊!?
其他到底还有什么变量啊?明明都计算好了才对。我不断拍着头懊恼,试图掩饰自己的尴尬。
“真的只是内部原因吗?”他轻声问道。
“你的意思是还有外部因素?作为银河系边缘的文明,你们不过是将将被发现的遗迹文明罢了,若是早先有你们那些脑补的外星人入侵,我们应该早就知晓了此地才对。至于什么伽马射线爆、超新星爆发就更别想了,这一荒芜的地带一直都很和平,残留的迹象也与那些毫不相关。”
“会不会是这些星星?”他伸出手去,把周围的几颗星星揽来。
“啊,星星的确是我偷工减料了,毕竟作为课题作业也太麻……不管这个,但是这些你们根本没有探索到啊,而且星图我也是严格地按照真实情况排布而成。”我挥手将眼前的星点们缩小成图状。
“你们是最近才发现我们毁灭的文明,那么之前的星图完全正确吗?”他继续表达着自己的怀疑。
“就算不正确也当差不多啦,文明毁灭的奇点可不会因为几颗星星的排布而改变。”
“可以给我看看此前的星图吗?”他仍旧锲而不舍地追问。
我无奈地将星图以亿年的尺度向前回溯,他则划出投影设备比对着不同。
“这里,为什么太阳系不在?”他在图上点出一个位置,回过头来。
“当时还未怎么探索银河系的边缘地带,所以也因此没能发现你们的文明啊。”我不在意地回道。
“如果太阳系此前就不在银河系呢!?”他大胆地提出了设想,那日回去后他应该思考了很多吧。
嗯?我有些不解。
“也许太阳系本来就不在这里,而是因为某种原因使得它的轨迹恰好途径银河系的边缘而被其引力所俘获。而在那时人类的文明便早已毁灭了。这样你所布置的星图与实际情况就会大相径庭吧!”他面红耳赤的样子显得十分认真。
“唔,照你这么说,以其公转速度和假设的时间点推演出原先的情况倒也未尝不可。”
我催动起程序进行运算,包裹着太阳系的奥尔特云绕银河系反向旋转,并开始脱离。他双目紧盯着投影,担心着自己的猜测。
不断远离着壮阔银河的奥尔特云像是遇到了阻塞,开始放缓起来。
“黑域!”我惊呼道。
显然他有些不解,期待着我的解释。
“那是宇宙的洼地,陷进去的一切都不会接收到外界的光线,就像是被空间扭曲发散开。你说对了,我的星图完全错了,因为那里根本就没有星图。”我拍了拍他的肩膀,以示鼓励。
“但是仅仅就因为无法到达的星光?按理说应该不足以影响奇点吧。”我还是不怎么相信这个事实。
“别太小看人类对星空的幻想了。”他反击道,并从书架上取下他此前在看的书本。
《海伯利安的陨落》,我随意地扫了一眼。
“这本书我很喜欢的哦,关于宇宙中人类与幻想的故事。如果没有星空的话,它也未必会诞生吧。在那个已经毁灭的地球上,可不会有牛郎织女的浪漫,也不会有星座与星云的绮丽,更不会有永不寂灭的北辰,哪怕是丝毫包含星宿的传说都会分崩瓦解。如果剥夺了人们这片寄托了大半的好奇、幻想,乃至信仰的星光后,那个世界还能剩下什么呢?”他的话语中像是带着对现今的庆幸。
正是因为能够借此认识到自己的渺小,人类才能够不断奋发前进的吧。
我有些不好意思打断他的感慨,但我想还是不得不说出来。
“其实你猜的到吧,无论模拟成功还是失败,世界毁灭与否,这个世界终究还是会消失。因为我差不多该重启这世界,推算方才新的猜想了。太阳系是如何离开这片黑域的,我还并不知晓。不过谢谢你的提示啦,小仓鼠。”
他没有再反驳我的称呼,像是在低头做着自己的思考。
“当人类开始仰望夜空时,空无一物的世界会让人失望吧。”他的声音越来越低,像是只说给自己。
一开始就没有连其存在都不知道的东西反而是不会失望的,失望的只有拥有过却又失去的东西罢了,这才是人类啊。这样的话,我还是没好意思说出口。
“不过总而言之,谢谢你让我能够拥有关于这片星空的幻想。唔,还是应该照惯例说谢谢款待吗?那么你开始吧。”他抬起头来,带着微笑正视着我。
我看得出他对此前自己的秘密只口不提,但是真的以为能够隐瞒过我吗?当然现在追问的话也太过分了,我拿起浮在空中半睡半醒的小仓鼠交予他,“那它就送你照顾了,再见。”
“你至今都没有告诉我它的名字啊。”他无奈地盯着手中的小仓鼠苦笑道。
“有了名字,再分别的话,会更让人伤感的吧。”我将这副虚拟的身躯散去,准备开启下一个世界的轮回。
旧世界如所料般,分崩离析。
## 无尽的交响乐章
年末,我向学院提交了假期的课程报告,一种生活在黑域中的人类生物,因害怕资源匮乏而在文明的末期试图实现戴森球*(注:戴森球是一种设想中的巨型人造结构,由弗里曼•戴森先生提出。这样一个“球体”是由环绕太阳的卫星所构成,完全包围恒星并且获得其绝大多数或全部的能量输出。)*,却因计算失误,引爆行星以制造材质的过程中使得星球所在轨迹偏离,而导致整个星系平衡破坏开始相对移动,最终脱离黑域。但其上的生物也在脱离的过程中被引力场与辐射所灭亡。
课程结题后,我突然回想起那如小仓鼠般的人类,那日告别时说的可是再见而非也别,因为我知道会有那初见般的重逢。虽然渺小,但是似乎也有属于他们的独特之处呢。
那么就让我看看,拥有星光与所谓幻想的人类究竟能谱写出怎样华丽的乐章吧?
当然,还有他未言的秘密。
于是,停留在我室内角落的信息流再次开始流转,亘古不迭。
> “呐,你说世界上真的有外星人吗?”我轻声提问道。
> “也许,你手中的仓鼠就是哦。”他笑着回过头来,如往常一般。

View File

@ -0,0 +1,136 @@
---
title: 第二章 白昼之光,岂知夜色之深。
layout: collection
---
> 我们之所以为人,是因为我们凝视星空。还是因我们是人,所以才凝视星空。
> 我似乎在自命名为人类生物的某种娱乐产物中,听到过这样句话。事实上倘若没有这片星空,它也断然不会诞生。
> 所以星空也在凝视着人类。
---
> “呐,你说世界上真的有外星人吗?”我轻声提问道。
> “也许,你手中的仓鼠就是哦。”他回过头来,就如往常一般。
---
## 混沌理论
第 42 次。
不论第几次提出这样的问题,得到的都是这般大抵相同的回答。虽然此间也有几次在轻嘲着我的异想天开,但我再也没有听到与那次相同的推论。
这仍旧不是他。
混沌理论。
虽然理论上奇点进程不会有所出入,但作为对历史潮流几无影响的小人物,却会因为各种各样的随机因素导致行为乃至性格都发生变化。这也便是所谓的蝴蝶效应。
即便发展到我等高级文明,也无法彻底掌控。而这个世界加之星空本身便是基于混沌理论的框架衍生而出,只要细小的变量发生变动,都几乎不再是完全相同的世界。
那么究竟又是哪里设置的变量不对?提问的时间?摸仓鼠的动作?明明哪怕是书架上摆放的书籍位置,我都没有变动过。而且课上再复杂的实验程序,也只要重复一遍代码,便绝对能获得相同的返回值。
所以我才讨厌所谓的弱类型编程语言,总是自顾自地猜测解释,却不在变故处提示。还有这个破绽百出的开源模拟框架,也许我可以给他们提一些 Issues 。但不可否认的是,正因为无法预测的混沌理论,才使得这个世界充满惊喜。
还要回滚到上一时刻继续模拟吗?在这种无聊的事情上花费时间,并不是什么明智的选择。为什么要执着于这个普普通通的人类呢?我左手轻抱着仓鼠,同时用空出的右手在半空中划开操纵界面,准备登出这个世界。
“顺带一提,外星人是绝对存在的哦!比如我就是~”我说着这话的同时,将菜单已然翻了一个来回。
镇定力不错,没有如第 30 次中那样掉落手中的书籍,我暗暗地给予了褒奖。
但根本不是关心这个的时候。我把左手的仓鼠也放了下来,再次翻找起来。
果然……
登出按钮好像不见了。
## 天亮了
天……还没有亮么?
望着周围昏暗的世界,我缩了缩脑袋,继续安心地蜷缩起来。
好暖和。本想借此感慨打一个舒服的哈欠,却未能发出相配的声音来,难免有些不爽。
但却让我略微清醒起来,这样绝对不行,明明计划好要每天锻炼身体的,继续怠惰下去,恐怕只会变成夜行性生物。虽然我本来便是如此。
我努力睁开沉重的眼皮,拖着懒散的身体,向记忆中跑步机的方位爬去。
「不在?」
我直了直身子,试图用手揉开那惺忪睡眼。我的视力已经差到这种地步了吗?
什么都没有,这个世界仿佛空无一物。
要是有光就好了,夜行性的我第一次如此期盼起天亮。
当然那是在其到来之前。
我将将抬起头来,沉浸于黑暗中的视锥细胞还来不及准备,那从远处极速驶来的光暗分界线便将我就此吞噬了。
---
是 BUG 吗?明明是开源的代码,竟然会出现这种问题?
在这里我所拥有的,只是基础的一些权限而已,就算想要修改代码,也没有现成的编译器。不过,既然是开源,那么代码的变更要不了多久就会被发现的吧。只是多等待些时间罢了。
只是多等待些时间罢了。这是我们悠长岁月里最不缺少的东西。
“那么我也是哦~”差点被我忘记的他,突然回应起我的话来。
无数的谍战画面与阴谋论在我的脑海里一闪而过,让我不由得兴奋起来。
“莫非是你干的?……”我猛然回过头来,正视起他。
啪嗒。
他握在手中的书本,终于还是掉落了下来。
“什……么?”像是被我的声势与眼神给吓到了,他发出的疑问话语则有些结巴。
一般到这时候,我早已退出了这个世界,根本不会再有接下来的对话,所以现在的他相对我而言已是一个全新陌生的人了。
“你刚才的话是什么意思?”我追问道。
“不是开玩笑吗?”他对我突如其来地正经也有些疑惑,不得不尴尬地挠着头。
显然,是我多虑了。不过,这样似乎也很有趣。
“我可是货真价实的外星人,如假包换。”我挺起身来,拍了拍胸脯,带着前所未有的得意。和低级文明生物展示着自己的我,就像是在小学生算术比赛中拔得了头筹一样开心。
又或者是在举重锦标赛上,当着蚂蚁面,举起了更大块的石子?虽然不是一个公斤级的就是了。
欸,等等,你倒是好好听我说啊!
他默默地捡起那落在地上的书本,翻回此前看到的页数,继续读了起来。
……
“我可不是在开玩笑啊!”只吼到一半,我便发现了自己的失态,音调也随之降了下来。今天倒要让你看看所谓形而上的存在,我走上前去,拉起他的臂膀,准备故技重施。
“请等一下。”他像是感受到了我的架势,急忙说道。
“怎么了?”我也停了下来,难道他知道我要做什么了吗?
只见他慢条斯理地将正打开着的书本插上书签,轻轻合起,放在一旁。
“可以了。”
“都带上也是没问题的。”
不再等待他的回答,屋内零零散散的家具便连同我们,一起向上方的玻璃幕墙升去。不过更像是本踩在脚下的玻璃地板突然陷了下去。
看到他那快要惨叫出的样子,我不禁笑了出来。故作沉着,在这时也是绝对做不到的吧。
“要撞……”
不等他字句吐完,我们便轻而易举地穿过了那巨大玻璃样式的天花板。
而下方闪过的则是熟悉的城市与云层,以及蓝色水球。这种加速度,如果按照正常物理法则的话,在穿越大气层时被燃烧殆尽也不足为过。
“欢迎光临……”
但谁让我是这里的主宰呢。称我为唯一神,也绝不足为过。
“我的——”,我拖长了音,等待他在这深邃的太空中调整好自己的姿态。“宇宙。”

View File

@ -0,0 +1,78 @@
---
title: 第三章 作茧自缚
layout: collection
---
## 因为我是神,所以让我们毁灭世界吧。
“没错,我是外星人,同时也是你们所谓的这个世界的神。”还要重新解释一遍,还真是麻烦。虽然也有不去管他,或者抹消记忆之类的选择,但总觉得似乎不大友好。我可是讲究人道主义的好公民。
“唯一神。”我装作毫不在意地补充道。
话说还会有鼠道主义这种东西吗?我稍稍提高了本地环境光的亮度,寻找起那不知被我放到哪儿的小仓鼠。
“不可思议……”他努力使自己的身体保持直立的状态,但似乎徒劳无功。不过能够从各个角度欣赏着这瑰丽的景色,也算是幸事了。
本该漆黑寒冷无比的宇宙,却被那近在咫尺的散发着光与热的恒星气团点缀起来。稍远处的星系也不甘示弱,闪耀着绚丽的色彩,而实际尺寸并不大的星云则朦胧地透出其难以言喻的壮阔。一切都与上次和他到来此地的景色别无二致,当然除了周围多出来的漂浮着的家具物件。虽然只是假象,但这一切都本不应为他们所有。
“名为人类的这种生物,早在两三万年前便已经灭亡了。现在的你们,只不过是我模拟数据中的一串串字节流罢了。”我拨开附近漂浮着的转轮和食槽,其后的却是裹成一团的毛线。
“为……什么?”他自己都不知道想提的是什么问题了吧。
“作茧自缚咯。”我找到线团的线头,将其抽了过来。
“那么,这个世界很快就会毁灭是吗?”他放弃了姿体调整的努力,任由自己漂浮其中。
线团上的毛线随着我的抽拉,开始一根根松开,露出里面包裹着的家伙。找到了。这应该是在上升的过程中缠上的。
因为惯性,还在不断旋转着的小仓鼠像是跳着华尔兹,但它依旧熟睡着。这家伙,每天至少得睡二十个小时吧。
“没错。本该如此。”我停住小仓鼠的旋转,将其抱了过来。
“本该如此?”
“意思就是你暂时不用杞人忧天啦。而且,”我缓慢地调整着姿势,转过身来,指向下方蓝色的星球。“这里有你在乎的人吗?”
他脸蓦地红了起来,支支吾吾地没有回答。
大概又在胡思乱想,我顿了顿,清了清嗓子。“我是说,你有想过你为什么没有家人以及朋友吗?”
话音刚落,他那惊讶的表情便溢于言表。
“是的,你所认识的人只我一人罢了。从最初开始,你便只是一个以人类的数据结构形式进行实例化的对象。所谓的记忆也只是我填入其中的数据,你的诞生就如同实例化一只仓鼠那样简单。”
“也就是说,我那一到午后,就会来此读书……”
“没错。都是我设定的程式罢了。”
“可是一到午后,我就会感到幸福的那种感觉也……”
“没错。”我打断他的问题,抢先答到。虽然我没有亲自设置过这种东西,不过这也只是代码附带的影响吧。
“顺带一提,真正的宇宙是远比你现在所见的黑暗、寒冷、寂静,乃至残酷的。没有缤纷瑰丽的星河,没有恰到好处的光热,也没有可以传达声音的空气,更别说像我这么友好的外星人了。”稍微有点在卖瓜的感觉,导致自己都有些不好意思。
“你们所拥有的资源在不断减少,所以害怕灭亡,想要逃离。而这也正加速了你们的灭亡。”对我这乱七八糟的话语冲击,他也差不多该适应了吧。虽然原本看起来有些失落的样子,但他现在似乎更沉浸于原本触不可及的星空。
“本来这个世界的运行,便只是为了还原你们灭亡的真相而已。而我已经知晓了原因,所以你们还可以看我的心情继续存活下去。当然前提是在我的工作站所能承受的运算量范围之内。”
“那么为什么还要和我多费口舌呢?”他漂在一颗散发着黄色光芒的恒星气团前,手放在胸前却没有任何动作,似乎在犹豫着要不要直接触摸。
“因为无聊啊。放心摸吧,温度是很适宜的,这个宇宙被我调整的可是相当温柔啊。”
现在 BUG 还没解决掉的话,可就太不像话了。我再次划开菜单,与此同时,他则用双手将那颗恒星小心翼翼地捧了起来。
“好暖和。”虽说我调高了这广阔太空中的温度,但相对于那颗星球上我们原本所在地的炎炎夏日,还是要低了不少。而这不断散发着光与热的恒星气团正是合适的取暖工具。
“另外最主要的原因就是我暂时也出不去了。”没有,还是没有登出的途径。没有任何新的消息提示,也没有什么可以向外联系的手段,就如同被完全隔绝在此地一般。
不过我也没有什么要紧的事情就是了,虽然很像是卷入了什么通天大阴谋,但发生了这样的事情也应该很快会被其他人察觉,且得不偿失,或者说根本看不出有什么可以得到的利益。
“出不去……是指无法离开这个世界吗?”他抱着网球大小的恒星转过身来,你这是理所当然地把它当暖手宝了吗?
“嗯。出了一些变故……无聊啊无聊。”我晃动着肢体,在这看似漫无边际的深空中摇摆着。
因为角度的变换,视野中熟睡的仓鼠与他以及远处的蔚蓝星球恰巧构成了一条直线,一些奇怪的想法似乎在脑海里诞生了。
“喂!”一直以来这么喂喂地称呼他还真是不礼貌。
“我们毁灭世界吧。”

View File

@ -0,0 +1,22 @@
import { defineCollection } from 'valaxy'
export default defineCollection({
key: 'hamster',
title: '仓鼠',
cover: 'https://cover.sli.dev',
description: 'The story of I and She',
items: [
{
title: '第一章 仓鼠的笼子',
key: '1',
},
{
title: '第二章 白昼之光,岂知夜色之深。',
key: '2',
},
{
title: '第三章 作茧自缚',
key: '3',
},
],
})

View File

@ -1,3 +0,0 @@
---
title: 第一章 我与她的邂逅
---

View File

@ -1,5 +0,0 @@
import { defineCollection } from 'valaxy'
export default defineCollection({
name: '我与她的故事',
})

View File

@ -1,4 +1,7 @@
---
layout: collections
icon: i-ri-gallery-view
collections:
- hamster
- love-and-peace
---

View File

@ -1,5 +0,0 @@
import { defineCollection } from 'valaxy'
export default defineCollection({
name: '爱与和平',
})

View File

@ -0,0 +1,8 @@
import { defineCollection } from 'valaxy'
export default defineCollection({
key: 'love-and-peace',
title: '爱与和平',
cover: 'https://cover.sli.dev',
description: 'The story of love and peace',
})

View File

@ -59,6 +59,7 @@ declare module 'vue-router/auto-routes' {
'/guide/features': RouteRecordInfo<'/guide/features', '/guide/features', Record<never, never>, Record<never, never>>,
'/guide/getting-started': RouteRecordInfo<'/guide/getting-started', '/guide/getting-started', Record<never, never>, Record<never, never>>,
'/guide/i18n': RouteRecordInfo<'/guide/i18n', '/guide/i18n', Record<never, never>, Record<never, never>>,
'/guide/layout': RouteRecordInfo<'/guide/layout', '/guide/layout', Record<never, never>, Record<never, never>>,
'/guide/markdown': RouteRecordInfo<'/guide/markdown', '/guide/markdown', Record<never, never>, Record<never, never>>,
'/guide/page': RouteRecordInfo<'/guide/page', '/guide/page', Record<never, never>, Record<never, never>>,
'/guide/post': RouteRecordInfo<'/guide/post', '/guide/post', Record<never, never>, Record<never, never>>,

View File

@ -8,9 +8,9 @@ const { t } = useI18n()
<Layout>
<RouterView v-slot="{ Component }">
<component :is="Component">
<template #default>
<div class="prose mx-auto w-full max-w-4xl">
<h2 :id="t('post.contributors')">
<template #main-content-after>
<div class="prose mx-auto w-full max-w-4xl mb-4">
<h2 :id="t('post.contributors')" class="mt-4!">
<a :href="`#${t('post.contributors')}`" class="header-anchor" />
<span>
{{ t('post.contributors') }}

View File

@ -0,0 +1,90 @@
---
title:
en: Layout
zh-CN: 布局
categories:
- guide
---
框架 API 目前默认支持以下布局,布局支持与最终表现通常与主题有关。
- `post`:文章布局
- `tags`:标签布局
- `archives`:归档布局
- `categories`:分类布局
- `collections`:合集布局
## 使用布局
### 合集布局
新建总览页 `pages/collections/index.md`,并指定布局为 `collections`
- `collections`: 合集列表,须指定唯一合集 ID。
```md [pages/collections/index.md]
---
layout: collections
icon: i-ri-gallery-view
collections:
- hamster
- love-and-peace
---
```
新建合集文件夹 `pages/collections/hamster/`
- `index.ts`:合集配置文件。
- `1.md`:合集中的第一篇文章。
定义当前合集信息:
```ts [pages/collections/hamster/index.ts]
import { defineCollection } from 'valaxy'
export default defineCollection({
key: 'hamster',
title: '仓鼠',
cover: 'https://cover.sli.dev',
description: 'The story of I and She',
items: [
{
title: '第一章 仓鼠的笼子',
// 文章唯一索引,对应路径为 `pages/collections/hamster/1.md`
key: '1',
},
{
title: '第二章 白昼之光,岂知夜色之深。',
key: '2',
},
{
title: '第三章 作茧自缚',
key: '3',
},
]
})
```
新建合集中文章:
```md [pages/collections/hamster/1.md]
---
title: 第一章 仓鼠的笼子
layout: collection
---
```
效果预览:[合集Valaxy Theme Yun](https://yun.valaxy.site/collections/hamster/1)
## 实现布局
如 [valaxy-theme-yun](https://github.com/YunYouJun/valaxy/tree/main/packages/valaxy-theme-yun) 自 `v0.25.9` 支持了 `collections` 布局。
按约定,主题需要在 `layouts` 目录下创建对应的布局文件,文件名与布局名称相同。
在主题中,你可以使用以下合集相关 API。
- `useCollections` API 获取合集列表。
- `useCollection` API 获取单个合集(根据路径判断当前合集 ID
<<< @/../packages/valaxy-theme-yun/layouts/collections.vue

View File

@ -119,8 +119,9 @@ password: valaxy
::: zh-CN
- `toc: false`: 隐藏目录
- `sidebar: false`: 隐藏左侧文章导航栏
- `aside: false`: 隐藏右侧文章导航栏
- `toc: false`: 隐藏目录
- `codeHeightLimit: 300`: 代码块高度限制300px
:::
@ -129,8 +130,9 @@ password: valaxy
::: en
- `toc: false`: Hide TOC
- `sidebar: false`: Hide Left Sidebar
- `aside: false`: Hide Right Aside
- `toc: false`: Hide TOC
- `codeHeightLimit: 300`: Code block height limit300px
:::

View File

@ -6,7 +6,7 @@ import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const { page } = useData()
const date = computed(() => new Date(page.value.lastUpdated!))
const date = computed(() => new Date(page.value?.lastUpdated || 0))
const isoDatetime = computed(() => date.value?.toISOString())
const datetime = ref('')

View File

@ -69,9 +69,8 @@ onContentUpdated(() => {
<slot name="main-content-md" />
<slot />
</ValaxyMd>
<PressDocFooter v-if="!isHome" class="pb-8 max-w-4xl" w="full" m="auto" />
<slot name="main-content-after" />
<PressDocFooter v-if="!isHome" class="pb-8 max-w-4xl" w="full" m="auto" />
</slot>
</div>

View File

@ -8,9 +8,19 @@ defineProps<{
<template>
<RouterLink
class="inline-flex rounded p-4 h-50 w-40 bg-gray-100 dark:bg-dark-300 shadow"
:to="`/collections/${collection.id}`"
:to="`/collections/${collection.key}/`"
:title="collection.title"
class="flex flex-col gap-2 hover:border-b-0!"
>
Book
<div class="inline-flex rounded h-40 w-30 bg-gray-100 dark:bg-dark-300 shadow hover:shadow-md transition overflow-hidden">
<img
:src="collection.cover"
:alt="collection.title"
class="flex size-full object-cover max-w-none"
>
</div>
<div class="text-sm font-bold text-center op-80 hover:op-100">
{{ collection.title }}
</div>
</RouterLink>
</template>

View File

@ -0,0 +1,76 @@
<script setup lang="ts">
import { useCollection } from 'valaxy'
const { collection } = useCollection()
</script>
<template>
<YunCard class="collection p-4 min-h-sm justify-start items-start" flex="~ col gap-1">
<section class="yun-sidebar-item">
<div class="title">
{{ collection.title }}
</div>
<div class="items">
<div v-for="item in collection.items" :key="item.key" class="item">
<div class="indicator" />
<RouterLink
:to="`/collections/${collection.key}/${item.key}`"
>
<p class="text">
{{ item.title }}
</p>
</RouterLink>
</div>
</div>
</section>
</YunCard>
</template>
<style lang="scss">
.collection {
.yun-sidebar-item {
.title {
font-weight: 500;
font-size: 16px;
}
.items {
border-left: 1px solid var(--va-c-divider);
color: #666;
font-size: 0.9em;
padding-left: 16px;
.item {
.text {
color: var(--va-c-text-2);
flex-grow: 1;
padding: 4px 0;
font-size: 14px;
line-height: 24px;
transition: color .25s;
}
.indicator {
border-radius: 2px;
width: 2px;
transition: background-color .25s;
position: absolute;
top: 6px;
bottom: 6px;
}
.router-link-active {
color: var(--va-c-primary);
font-weight: 500;
.text {
color: var(--va-c-primary);
}
}
}
}
}
}
</style>

View File

@ -18,7 +18,9 @@ const showLeftLayout = computed(() => {
v-if="showLeftLayout"
flex="~ col" class="gap-4 sticky top-$yun-margin-top w-80"
>
<YunSidebarCard />
<YunAdBoard />
<slot>
<YunSidebarCard />
<YunAdBoard />
</slot>
</div>
</template>

View File

@ -1,6 +1,6 @@
<script lang="ts" setup>
import { defineWebPage, useSchemaOrg } from '@unhead/schema-org/vue'
import { useCategories, useFrontmatter, usePostTitle, useSiteStore } from 'valaxy'
import { useCategories, useFrontmatter, useSiteStore, useValaxyI18n } from 'valaxy'
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
@ -36,7 +36,7 @@ const posts = computed(() => {
return list
})
const title = usePostTitle(frontmatter)
const { $tO } = useValaxyI18n()
useSchemaOrg([
defineWebPage({
@ -53,7 +53,7 @@ useSchemaOrg([
<component :is="Component">
<template #main-header>
<YunPageHeader
:title="title || t('menu.categories')"
:title="$tO(frontmatter.title) || t('menu.categories')"
:icon="pageIcon"
:color="frontmatter.color"
:page-title-class="frontmatter.pageTitleClass"

View File

@ -4,12 +4,23 @@
<template>
<YunLayoutWrapper>
<YunLayoutLeft>
<YunCollectionSidebar />
</YunLayoutLeft>
<RouterView v-slot="{ Component }">
<component :is="Component">
<div flex="~ wrap" gap="4">
Collection
</div>
<template #main-header-after>
<YunMainHeaderAfter />
</template>
<template #main-content-after>
<YunMainContentAfter />
</template>
<template #aside-custom>
<slot name="aside-custom" />
</template>
</component>
</RouterView>
<YunLayoutRight />
</YunLayoutWrapper>
</template>

View File

@ -1,19 +1,36 @@
<script setup lang="ts">
import { useCollections } from 'valaxy'
import { useCollections, useFrontmatter, useValaxyI18n } from 'valaxy'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const { $tO } = useValaxyI18n()
const fm = useFrontmatter()
const { collections } = useCollections()
</script>
<template>
<YunLayoutWrapper>
<YunLayoutLeft />
<RouterView v-slot="{ Component }">
<component :is="Component">
<div flex="~ wrap" gap="4">
<YunCollectionItem
v-for="collection in collections" :key="collection.id"
:collection="collection"
<template #main-header>
<YunPageHeader
:title="$tO(fm.title) || t('menu.collections')"
:icon="fm.icon"
:page-title-class="fm.pageTitleClass"
/>
</div>
</template>
<template #main-content>
<div flex="~ wrap" gap="6">
<YunCollectionItem
v-for="collection in collections"
:key="collection.key"
:collection="collection"
/>
</div>
</template>
</component>
</RouterView>
</YunLayoutWrapper>

View File

@ -1,5 +1,8 @@
import type { CollectionConfig } from '../define'
import { ref } from 'vue'
import collections from '#valaxy/blog/collections'
import { computed } from 'vue'
import { useRoute } from 'vue-router'
/**
* Composable for Collections
@ -7,22 +10,28 @@ import { ref } from 'vue'
* @example /collections/love-letters/1
*/
export function useCollections() {
// TODO
const collections = ref<CollectionConfig[]>([
{
id: 'i-and-she',
name: 'I and She',
description: 'Love letters from the past',
},
{
id: 'love-and-peace',
name: '爱与和平',
description: 'Recipes for a good life',
},
])
return {
collections,
collections: computed(() => collections),
}
}
/**
*
*/
export function useCollection() {
const route = useRoute()
const collectionId = computed(() => {
if (route.path.startsWith('/collections/')) {
return route.path.split('/')[2] // 获取集合 ID
}
return ''
})
const collection = computed<CollectionConfig>(() => {
return collections.find(item => item.key === collectionId.value)
})
return {
collection,
}
}

View File

@ -1,9 +1,12 @@
export interface CollectionConfig {
title?: string
key?: string
/**
* auto-generated by your collection path
* @example /collections/love-letters/1 => id: 'love-letters'
* if key is not provided, path is required
*
* if key is provided, path = `/collections/${key}`
*/
id?: string
path?: string
/**
* @en
* The name of the collection.
@ -16,6 +19,19 @@ export interface CollectionConfig {
description?: string
categories?: string[]
tags?: string[]
/**
* items
*/
items?: {
title?: string
/**
*
*
* `/collections/${key}/${item.key}`
*/
key?: string
}[]
}
/**

View File

@ -26,6 +26,7 @@ menu:
categories: Categories
tags: Tags
posts: Posts
collections: Collections
about: About
search: Search

View File

@ -26,6 +26,7 @@ menu:
categories: 分类
tags: 标签
posts: 博客文章
collections: 合集
about: 关于
search: 搜索

View File

@ -7,4 +7,12 @@ declare module 'virtual:valaxy-theme' {
declare module '#valaxy/styles' {
// side-effects only
export default string
}
// valaxy features
declare module '#valaxy/blog/collections' {
const collections: Collection[]
export default collections
}

View File

@ -164,16 +164,19 @@ export function getDefine(_options: ResolvedValaxyOptions): Record<string, any>
export async function getAlias(options: ResolvedValaxyOptions): Promise<AliasOptions> {
const alias: Alias[] = [
/**
* virtual module alias
* `/` declare module 类型
*
* #valaxy/* => /@valaxyjs/*
*/
{ find: /^#valaxy\/(.*)/, replacement: '/@valaxyjs/$1' },
{ find: '~/', replacement: `${toAtFS(options.userRoot)}/` },
{ find: 'valaxy/client/', replacement: `${toAtFS(options.clientRoot)}/` },
{ find: 'valaxy/package.json', replacement: toAtFS(resolve(options.clientRoot, '../package.json')) },
{ find: /^valaxy$/, replacement: toAtFS(resolve(options.clientRoot, 'index.ts')) },
{ find: '@valaxyjs/client/', replacement: `${toAtFS(options.clientRoot)}/` },
// virtual module alias
{
find: /^#valaxy\/(.*)/,
replacement: '/@valaxyjs/$1',
},
// import theme
{ find: 'virtual:valaxy-theme', replacement: `${toAtFS(options.themeRoot)}/client/index.ts` },
{ find: `valaxy-theme-${options.theme}/client`, replacement: `${toAtFS(resolve(options.themeRoot))}/client/index.ts` },

View File

@ -0,0 +1,47 @@
import type { VirtualModuleTemplate } from './types'
import path from 'node:path'
import fs from 'fs-extra'
import { toAtFS } from '../utils'
/**
* blog features
* @param name
* @returns
*/
function createBlogTemplate(name: string): VirtualModuleTemplate {
return {
id: `/@valaxyjs/blog/${name}s`,
async getContent({ userRoot }) {
// glob collections/${collectionId}/index.ts
const root = path.resolve(userRoot, 'pages', 'collections')
if (!(await fs.pathExists(root))) {
return `export default []`
}
// 是目录
const isDir = (file: string) => fs.statSync(path.join(root, file)).isDirectory()
// 读取目录下的所有文件
const files = fs.readdirSync(root).filter(file => isDir(file)).map(file => path.join(root, file, 'index.ts'))
const imports: string[] = []
const getImportedName = (idx: number) => `__valaxy_${name}_${idx + 1}`
files.forEach((file, idx) => {
const importedName = getImportedName(idx)
imports.push(`import ${importedName} from '${toAtFS(file)}'`)
})
// return array
imports.push(`export default [${files.map((_, idx) => getImportedName(idx)).join(', ')}]`)
return imports.join('\n')
},
}
}
/**
* collections: collections/${collectionId}/index.ts
*/
const configs = [
'collection',
]
export const templateBlogs = configs.map(createBlogTemplate)

View File

@ -1,4 +1,5 @@
import { templateAddons } from './addons'
import { templateBlogs } from './blogs'
import { templateConfig } from './config'
import { templateLocales } from './locales'
import { templateStyles } from './styles'
@ -8,4 +9,6 @@ export const templates = [
templateConfig,
templateLocales,
templateStyles,
...templateBlogs,
]

View File

@ -1,4 +1,5 @@
import type { ImageObject, NodeRelations } from '@unhead/schema-org'
import type { CollectionConfig } from '../../client/define'
export interface Album {
/**
@ -160,17 +161,21 @@ export interface PageFrontMatter extends Record<string, any> {
*/
medium_zoom: boolean
// --- layout ---
/**
* @description:en-US Albums
* @description:zh-CN
*/
albums: Album[]
/**
* For layout Gallery
* @description:en-US Photos
*/
photos: Photo[]
/**
* for collections
*/
collections: CollectionConfig[]
/**
* @description:zh-CN password true