MoreRSS

site iconAsyncX修改

面向兴趣编程,养猫,润加拿大。INTJ/P。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

AsyncX的 RSS 预览

4-7月小结-庸庸碌碌

2025-07-11 21:07:00

前言

忙于学业, 好久没有更新. 为了追上之前落下的学分, 我选择在summer term报了full-time, 又因为夏季的学期有2段, 所以有2门课程集中在了第1段, 这就类似于我在一个正常的学期一周上5门课,每门课又有1/2个LEC课和1/2个LAB课. 第一段学期对我来说实在是太难了. 好在磕磕碰碰的结束了这个学期, 除了考试我感到不满意外, 我的作业和项目几乎都拿到了高分以至于满分. 一定程度上这对我来说也是一种鼓励吧.

消费主义陷阱

4个月前称体重的时候, 我的体重还一如既往的是65kg. 上周称体重的时候, 已经69kg了. 上一年爬Saint-Bruno的时候, 也是我有史以来第一次爬山爬到上气不接下气, 心率一度到200. 刚好看到Garmin的官网在打折, 于是买了一块Garmin Fēnix 7 Pro Sapphire Solar.

到手后跑了一下, 发现我的身体机能下降到了一个不敢想象的地步.1km一圈, 跑了3圈, 配速是8' 8' 7'. 第三圈的末尾我的心率从140断崖式上升到200. 很难想初中高中那时候的跑操是怎么跑下来的.

很多专业的数据我还没有了解清楚, 我目前看的指标有: BPM(平均心率, 运动的时候看)和Body Battery(实时显示的身体电量, 休息和睡觉会充电, 压力和运动会消耗身体电量). 此外, Garmin会根据你的上述指标和其他状态来告诉你今天的理想运动内容. 目前我只用到了一部分的功能, 其他的还没有琢磨.

第二段学期只有1门课, 所以我打算在每次下课后去爬Mont-Royal来增强心肺功能.

最后, 我一如既往的折腾了一个运动网页. 本来计划想用yihong0618/running_page这个repo来实现, 但是我并不想展示轨迹, 于是在了解了Garmin connect API的原理后自己动手写了一个. 功能很简单, 具体来说就是Python模拟登录来获得运动数据的json并处理成.csv, 存储到src/component中, 前端渲染交给Astro来实现. 我会在晚些时候单独写一篇文章. 默认页详情数据展示

运行交给了Github, 部署交给了Cloudflare. 使用Actions, 在每日凌晨自动读取数据, 触发Cloudflare更新.

生活

我现在住的地方是外公外婆的房子, 天气变暖他们就从国内回来了. 所以我自然而然失去了厨房的所有权. 现在顿顿吃得极度清淡, 只能寄希望于和朋友们出门吃点口味重的. 考完试后, 我们一起去了昆虫馆:

书影音游

++game/5pvs201VxbkldH4LOEtDVt++

++game/35pyo0ZwNaEsTGrywLIsJ1++

++movie/3pzmdXn8JRf4xIulfvb9Ef++

++movie/6Vu6XIJIMSY2WWWobxj9r1++

Astro-blog记录(6)-制作一个自同步运动数据页

2025-07-11 21:07:00

前言

本文的实现并没有包含路径展示,分年统计的功能, 如果你需要更成熟的方案, 可以使用yihong0618/running_page.

前几天购入了一块佳明, 在进行几次运动后一如既往的想要折腾一下, 就有了写一个运动数据页的想法. 我的需求很简单, 有一个总体数据展示, 并且可以兼顾隐私性(数据太多不会读)和扩展性(万一以后想加点什么). 所以初步功能就定为了:

  1. 用一列数据来展示已经运动了多少
  2. 用一个表格来汇总所有运动的细节
  3. 添加一个"更新日期"方便观察运行情况

本文基于Python模拟Garmin connect登录以获得运动数据, 并存储为csv文件, 之后使用Astro基于csv文件进行前端页面的渲染. 其中自动化运行Python是通过Github Actions完成的. 你可以访问我的库来获得详情: A5yncX/my_running

1. 运行环境

由于佳明开启心电图功能后需要二次验证(邮箱验证码/短信验证码), 并且在短时间内多次的登录会触发cloudflare的风控, 所以本文方法是基于登录生成的token登录. 你需要如下包:

pip install garminconnect httpx cloudscraper

接下来, Fork我的仓库是最快的方法, 如果你并不想fork, 也可以点击Code -> Download zip到本地修改并继续运行.

2. 首次运行的准备

在项目目录运行:

python3 running/garminconnect_exporter.py --username YOUR_MAIL --password YOUR_PSWD

这一步的作用是在本地生成2个JSON格式的Token, 用来持久化登录, 有效期据github上的大家说是1年.

在你首次运行并接收到验证码后, 这两个文件会生成到位于你电脑的根目录的.garminconnect文件夹内, 文件的名字是oauth1_token.json/oauth2_token.json, 之后你运行一下命令就可以直接基于token进行登录了:

python3 running/garminconnect_exporter.py

首次运行后你会发现csv文件生成于src/components/activities.csv. 这就是后续使用Astro可以进行读取的文件了. 接下来的步骤需要你先推送到github.

3. (重要)(可选)Github Actions自动更新

如果你更倾向于手动更新, 那么恭喜你, 你可以略过这里跳转到部署那一步了.

本地运行garminconnect_exporter.py依托于本地的那2个token, 但是在Github上如果直接将这2个json的内容作为variable传入, 会出现换行符等问题导致没法正常使用, 所以需要用base64转换并存储, 接下来是详细步骤:

3.1 生成base64文件:

到本地.garminconnect文件夹的目录中, 你会发现那2个json文件, 分别运行:

base64 -i oauth1_token.json

base64 -i oauth2_token.json

分别复制这2个命令的输出, 并点击存储库的setting: Setting->Secrets and variables->Actions->New repository secrets 设置两个secret, 命名为GARMIN_OAUTH1_B64/GARMIN_OAUTH2_B64, 分别存储这2个命令的输出.

3.2 (可选)存储账号密码到secret

这一步的作用是方便没有邮箱/短信登录验证的人使用的, 以及如果后续token失效, 你的邮箱/短信会收到一次验证码, 可以起到提醒你更新token的作用. 依旧点击存储库的setting: Setting->Secrets and variables->Actions->New repository secrets, 设置两个secret, 命名为GARMIN_USERNAME/GARMIN_PASSWORD, 值就是你的登录账号密码.

3.3 试运行

删除github repo中之前生成的csv文件, 点击Actions手动运行一下, 查看是否正确生成.

4. 部署(Cloudflare)

侧边栏选择计算(Workers), 创建pages, 模板选择Astro, 默认的命令和路径不需要改变, 等待部署成功即可.

3月小结-自得其乐

2025-04-14 05:04:00

前言

距离上次更新已经1个多月了, 拖更的原因无非是学业, 以周为单位的代码实验课再加上偶尔的其他project也让我在很多时候应接不暇. 至于标题为什么是自得其乐, 是因为这个月没怎么进行不必要的社交, 这对一个I人来说是难能可贵的.

学业

我有想过这样的问题: 为什么每次小结我都要阶段性的提及我的学习方面? 这样的分类有没有存在的必要? 不过这个纠结点只存在了一会就消失了. 现在作为一个学生, 从任何角度来说都没有对学习略过不谈的理由. 而作为一个完全没有前车之鉴就出国读书的小白, 我自认为留下一些属于自己的记录是会对大家有帮助的.

言归正传, 在停更的这段时间里, 我经历了6个实验, 2个代码考试, 1个期末考试外加几个presentation. 其中的代码考试和实验全部都是一门java编程课, 这门课的教授要求如下:

  1. 上课不能使用任何电子设备.
  2. 在实验和代码考试中你只能手写代码, 期间断网+禁止使用电子设备.

那么实验和考试是如何开始的:

  1. TA在每次实验和考试(这两者的考察模式一模一样)开始前会先检查并确认你的电脑没法访问网络
  2. TA会发卷子并讲解题目, 卷子会提供input case和output case, 这和Leetcode上的写方法不同.
  3. 完成代码并提交, 系统会测试并给你反馈.

我在本科期间的实际java代码量少的可怜, 完全没有从0到1的完成过完整的代码, 所以在这门课开始的时候我很抓瞎, 至于抓瞎到什么程度: 第一次实验的内容是你需要对一个Array进行翻转和一些种类的遍历, 我当时甚至不知道java要怎么读取用户的输入(Scanner sc = new Scanner(System.in)),在和旁边的中国同学窃窃私语得到答案后, 我又不知道怎么创建Array, 甚至不知道怎么遍历. 满分10分得了1/10.

之后我狠狠的下了一番功夫, 实验和考试是在每周一的6:30PM开始的, 因为大家的变成水平良莠不齐, 经常考到11点才能回家, 因为我家离学校很远, 几乎每天回到家都是凌晨1点. 这里也留一下我对于这门课的心得: 我会在每周六看教授在交流软件上发布的关于下次考试的内容, 然后阅读教授的个人网站上的教学代码, 在过完所有的代码后结合考点去学习方法的写法和思路, 期间会用GPT学习一些代码写法上的简单问题, 我的prompt是请你基于费曼学习法为我讲解XXXX(在这里我十分感谢汪哥, 我们之前关于获取信息的一些聊天让我受益匪浅). 在搞清楚所有可能考查的知识点后, 我会用markdown生成pdf打印下来, 便于随时看. 为了日常的交作业和打印的方便和统一, 手搓了一个Typora输出pdf的主题(宋体+Times new roman)

这个教授平心而论, 很负责人且反馈迅速, 他能记住课上110+个学生每个人的名字,每次的考试也都会和同学沟通, 会去查看答题情况进而调整下次的实验/考试.

生活

正如前言所说, 这个月没有那么多无意义的社交, 我依旧把大量的时间花费到了怎么犒劳自己的肚子上了. 这个月我也是带着诸如"我做的卤猪蹄真的很好吃"/"我煲的汤真的很好喝"的想法在做饭:

还有在和大家聚餐时候做的腌笃鲜/可乐鸡翅.....

消费主义陷阱

因为我的手机膜告急, 不得不从淘宝买新的膜, 想着单独买有些亏, 遂再次步入了消费主义陷阱:

三菱UNI KURUTOGA自动铅笔

来到这里后因为考试/日常使用, 自动铅笔成了我唯一的文具. 我的上一根笔是三菱的α-gel 什么的, 宣传点是铅芯会随着每次写字自动旋转, 以保持字迹粗细的一致(很好用),和握住的那个凝胶能集中注意力(实际用下来真的很舒服, 并且用了2年没有任何老化问题). 这次更换后高强度使用, 得益于金属笔杆, 相比于上一根, 用起来更踏实, 字迹也更好控制了(妈见打, 就不放图片了)

CASIO B5600钛合金表壳+表带

这个表是好朋友在前些时候换了CASIO的农家橡树, 他没地方用也就寄给了我. 我的手腕实在太细了, 再加上他之前给我的是不锈钢壳, 想到快到夏天就顺便买了一个好评的小厂造的钛合金表壳+表带. 我在换钛合金前的称重是131g, 在换后的重量是100g, 和之前比舒服了特别多.

迄今为止我给这个表改过:

  1. 太阳能面板(b5000黑色原装)
  2. 原装stn反显(可视角度更大)
  3. 钛合金表壳+表带

书影音游

++tv/5n1XRGkYnDWyLdkhQXMd3H++

++movie/3rsuSP1f283c5CuJdxyMIt++

++tv/7XMBvKp7G4l7hoo6oX1PMG++

2月小结-悠闲生活

2025-03-01 05:02:00

前言

一眨眼也是进入了这学期的reading week. 和第一个学期的reading week相比大家都没有那么激动了, 这次就没有出门旅游, 只是简单的聚了个餐. 就像这篇博文的标题一样, 我认为这个月的主题就是悠闲.

生活

食物方面, 从有了自己的厨房以来就没有再亏待过自己的嘴巴. 自己在家琢磨各种各样的美食. 比如下图, 按照记忆中的味道去复刻大学时候和女友吃到的土豆鸡+虾/用Instantpot去炖的猪蹄/去朋友家做的大烩菜/豉汁排骨/可乐鸡翅+虎皮蛋.

这里留一个最满意的高压锅炖猪蹄的菜谱:

  1. 猪蹄切块, 葱姜料酒焯水,水开之后焯5分钟, 捞出过冷水.
  2. 加入Instantpot(高压锅), 生抽老抽, 少盐(1~2茶勺), 多糖(约6块冰糖), 2片香叶, 1个八角, 我额外加了一些花椒黑胡椒.
  3. High pressure 25min, 手动放气
  4. 转入敞口的大锅, 大火收汁至黏稠.

我在中国超市买了2个猪蹄, 大约是8CAD, 吃了4顿. 一开始还在担心这么多的汤汁到底要浇多久的米饭才能用完, 结果好吃到仅仅4顿就连汤带肉一起消灭掉了, 一点都没有剩下.

消费主义陷阱

上个月的月度小结里没有写到这个主题, 就在这里归纳一下: 本月入手了2个小玩意.

Kindle Paperwhite 6 32G

第六期-一些Kindle的使用方法

详情我写在了这篇当中, 我着重说一下使用体验. 使用场景大概是每天的通勤2h+晚上在床上看2h, 算是中等程度的使用. 新的一代据说是暖屏(没有参照物), 我习惯将色温设置在和Marginnote4的护眼色相近的10档. 和看手机屏幕比, 体验好了不止一点, 也变相的大幅提升了我的阅读时间, 这个月看的书得超过之前5年看的书了(笑).

Rog夜魔声音包(炸虾粒客制化)

这是从国内邮寄过来的, 大概上年年末的时候遇到一个学长要回国, 在小红书上卖全新的白夜魔和xbox手柄, 价钱折合人民币不到1100, 刚好身边朋友需要xbox手柄, 我就一起购入了. 之后也一直没有顾得上改这个键盘(北美客制化的发展没有竞争对手去卷, 和国内比少太多配件了), 直到情人节, 女友问我要什么我选了这个. 白夜魔(雪武白轴)+FR4定位板(夜魔声音包9件套) 和刚到手的声音相比, 键盘的声音更偏向于麻将音了. 我还额外买了圣熊猫的镀金卫星轴, 出乎意料的是夜魔原装的简易卫星轴的发挥全方位优于圣熊猫的卫星轴, 最后我还是换上了原装的简易卫星轴.

博客装修

更清晰的点赞追踪

我在arc浏览器code分类的侧边栏根目录下保存了Umami访客统计, 一般2~3天我都会看一下读者会从哪里访问. 最多的来源一般是V2EX, 其次是我之前在知乎上发布的一些教程, astro的官网和astro中国开发者的导航页, 再其次是google, 最后是赛博友邻的友链.

言归正传, umami可以添加一些自定义的event来方便追踪用户行为, 之前对于点赞的event只定义了一个Like标志, 即使能看到有读者点赞了文章, 但是没有办法显示读者点赞了哪一篇文章. 现在修改为data-umami-event=${fileName}-Like, 这样的好处是可以知道读者点赞了哪片具体的文章.

<open-heart href={`https://xxx/${currentUrl}/like`} data-umami-event=`${fileName}-Like` emoji="♥">♥</open-heart>

书影音游页

Astro-blog记录(5)-添加NeoDB影音页

小彩蛋

这个月的闲暇时间尝试了一下像素画, 于是有了about页面右下角的产物: 点击后会出现毛毛的丑照(毛毛就是我的长毛猫, 具体可以看这篇文章)

书影音游

++book/4bVED3OmX1AIAOy52Yxp4W++

++book/0Qv4iGUnpdGHu9GEqpP5xh++

++book/3BWmn3mhMrqIazr5ZO3pTa++

++movie/6Bob1fCW9QcEpZoL56X16n++

++tv/season/6ReFpsYl0zhcWdmgiCIOoO++

++tv/7in2q2PQbOkpEcgnqr5DeP++

Astro-blog记录(5)-添加NeoDB影音页

2025-02-12 23:02:00

前言

曾几何时也是非常眼馋一些赛博友邻的观影页, 但是碍于没有相关的经验(懒)和Astro缺少相关的教程一直没有考虑过实现, 一开始的时候是想着这是一个动态服务, 所以可能需要考虑第三方服务来实现. 在看了几个博主是如何实现的后, 初步有了一些想法, 即可以用GitHub Action来实现. 因此, 本文所介绍的实现方式为:

  1. 使用GitHub Action调用NeoDB的API, 定期爬取并存储为json文件到库中.
  2. 使用Cloudflare加速并添加自定义域名.
  3. Astro读取json文件并渲染前端.

1.生成NeoDB API Token

如果我的描述不够清晰, 或许你可以从官方文档找到答案.

首先打开终端, 输入以下指令, client_name随意,redirect_uris可以填写为你的博客url再加上/callback, website为你的博客链接, 即前者去掉/callback:

curl https://neodb.social/api/v1/apps \
  -d client_name=MyApp \
  -d redirect_uris=https://blog.asyncx.top/callback \
  -d website=https://blog.asyncx.top

之后会返回如下格式的内容, 你需要留存client_idclient_secret供后文使用: 接着, 你需要在浏览器访问如下的网页链接, 将[CLIENT_ID][REDIRECT_URI]替换成你的输入即可:

https://neodb.social/oauth/authorize?response_type=code&client_id=[CLIENT_ID]&redirect_uri=[REDIRECT_URI]&scope=read+write

如果没有问题则会弹出这样的图片, 点击Allow即可, 接着你会跳转你自己的网页, 在浏览器的链接栏你可以看到生成的code.

接着,使用以下指令来生成Token,其中新获得的code也需填写于其中:

curl https://neodb.social/oauth/token \ 
-d "client_id=CLIENT_ID" \ 
-d "client_secret=CLIENT_SECRET" \ 
-d "code=AUTH_CODE" \ 
-d "redirect_uri=https://example.org/callback" \ 
-d "grant_type=authorization_code"

生成Token这步, 我会报错access denied, 但我没有发现任何导致出现错误的问题, 如果有读者发现麻烦留言, 我会同步更新博文. 因此这步后我使用了测试Token. 生成的链接位于https://neodb.social/developer/ 点击Test Access Token即可生成.

在获得并存储了Token后, 接下来进入Github Action的设置中.

2.创建Github存储库和Action

首先创建一个新的GitHub存储库(我创建新库的原因是防止每天定时的更新会让本地的Obsidian频繁拉取更改/让历史记录更清晰), 点击Setting-Security(Secrets and variables)-Action-New repository secret, 将生成的Token填进去, NameNEODB_ACCESS_TOKEN 接下来点击Actions-Newworkflow, 复制下面的代码到新建的.yml文件中

name: Sync NeoDB Data

# 允许 GitHub Actions 拥有写入仓库的权限
permissions:
  contents: write

on:
  schedule:
    - cron: "0 17 * * *"
  workflow_dispatch:

jobs:
  neodb_sync:
    name: Sync NeoDB Data for multiple categories
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v3

      - name: Check JQ
        run: |
          if ! command -v jq &> /dev/null; then
            echo "jq is not installed. Installing..."
            sudo apt-get update
            sudo apt-get install -y jq
          else
            echo "jq is already installed."
          fi
          echo "WORK_DIR=$(pwd)" >> $GITHUB_ENV

      - name: Ensure local JSON files exist
        run: |
          # 定义需要同步的分类(移除 album)
          categories=("book" "movie" "tv" "game")
          mkdir -p data/neodb
          for category in "${categories[@]}"; do
            local_file="data/neodb/${category}.json"
            if [ ! -f "$local_file" ]; then
              echo "Creating default JSON for category: $category"
              echo '{"data": [], "pages": 0, "count": 0}' > "$local_file"
            fi
          done

      - name: Sync Categories Data
        run: |
          # 定义需要同步的分类(移除 album)
          categories=("book" "movie" "tv" "game")
          UPDATED=0

          for category in "${categories[@]}"; do
            echo "Processing category: $category"

            # 下载该分类第一页数据,用于获取 count 和 pages
            curl -s -X GET "https://neodb.social/api/me/shelf/complete?category=${category}&page=1" \
              -H "accept: application/json" \
              -H "Authorization: Bearer ${{ secrets.NEODB_ACCESS_TOKEN }}" \
              -o "${category}1.json"

            # 提取远程数据的 count 和 pages, 如果为 null 则设为 0
            remote_count=$(jq '.count // 0' "${category}1.json")
            pages=$(jq '.pages // 0' "${category}1.json")
            echo "Remote count for ${category}: $remote_count, pages: $pages"

            # 读取本地数据的 count
            local_file="data/neodb/${category}.json"
            local_count=$(jq '.count // 0' "$local_file")
            echo "Local count for ${category}: $local_count"

            # 对比远程和本地的 count 数值,若一致则跳过下载
            if [ "$remote_count" -eq "$local_count" ]; then
              echo "No update for ${category}. Skipping download."
              continue
            else
              echo "Update detected for ${category}. Downloading all pages..."
              UPDATED=1
              # 为当前分类创建临时目录存储下载数据
              mkdir -p "neodb/${category}"
              if [ "$pages" -gt 0 ]; then
                # 循环下载所有页数据
                for ((i=1; i<=pages; i++)); do
                  echo "Downloading ${category} page $i"
                  curl -s -X GET "https://neodb.social/api/me/shelf/complete?category=${category}&page=$i" \
                    -H "accept: application/json" \
                    -H "Authorization: Bearer ${{ secrets.NEODB_ACCESS_TOKEN }}" \
                    -o "neodb/${category}/${category}${i}.json"
                done

                # 利用 jq 合并所有页面数据为一个 JSON 文件
                jq -c -s '{
                  data: map(.data[]) | unique | sort_by(.created_time) | reverse,
                  pages: (map(.pages)[0]),
                  count: (map(.count)[0])
                }' neodb/${category}/*.json > "${category}.json"
              else
                # 如果 pages 为 0,则创建默认空数据文件
                echo '{"data": [], "pages": 0, "count": 0}' > "${category}.json"
              fi

              # 确保目标目录存在,然后将合并后的 JSON 文件复制过去
              mkdir -p data/neodb
              cp -f "${category}.json" "data/neodb/${category}.json"
            fi
          done

          # 如果所有分类均未更新,则退出工作流
          if [ $UPDATED -eq 0 ]; then
            echo "No categories updated. Exiting."
            exit 0
          fi

      - name: Git Add and Commit
        uses: EndBug/add-and-commit@v9
        with:
          message: 'chore(data): update neodb data for multiple categories'
          add: './data/neodb'

点击提交, 接下来你需要再次进入Actions并手动Run一下, 不出意外你就可以看到新生成的json文件了. 到这一步, 你已经存储了NeoDB的数据, 接下来就是前端工作了.

3.前端读取和渲染

到了这一步我全权交给了ChatGPT, 由于展示的内容实在很多, 我在尝试了几个想法发现效果不理想后, 最终确定的内容为:

  1. 展示基于日期的最近3个月内的影音.
  2. 分类展示基于日期的最新的20个影音.

我在博客的页面目录/pages下创建了新的路径文件/bmmg/index.astro, 这样最终的访问url为https://blog.asyncx.top/bmmg. 对于下面的代码来说, 你需要手动修改一下自定义域名后的json文件url.以及由于我使用了TailwindCSS, 你需要让GPT修改其样式为普通的CSS

---
import Layout from "../../layouts/Layout.astro";

// 定义各分类对应的 JSON 文件地址
const jsonUrls = {
  movie: "https://neodb.asyncx.top/data/neodb/movie.json",
  tv: "https://neodb.asyncx.top/data/neodb/tv.json",
  game: "https://neodb.asyncx.top/data/neodb/game.json",
  book: "https://neodb.asyncx.top/data/neodb/book.json"
};

// 定义分类中文显示名称
const categoryMap = {
  movie: "影",
  tv: "剧",
  game: "游",
  book: "书"
};

// 用于存储各分类的所有数据(不做时间过滤,取最新20条)
const grouped = {};

// 用于存储合并后的数据(仅包含过去3个月内的记录)
let mergedRecentItems = [];

// 计算当前日期三个月前的日期
const now = new Date();
const threeMonthsAgo = new Date();
threeMonthsAgo.setMonth(now.getMonth() - 3);

// 动态读取各 JSON 文件
await Promise.all(
  Object.entries(jsonUrls).map(async ([cat, url]) => {
    const res = await fetch(url);
    const jsonData = await res.json();
    // 对该分类所有数据按创建时间降序排序(不做时间过滤)
    const sortedAll = jsonData.data.sort(
      (a, b) => new Date(b.created_time) - new Date(a.created_time)
    );
    // 分类展示:取前20条,并附加所属分类信息
    grouped[cat] = sortedAll.slice(0, 20).map(item => ({ ...item, category: cat }));
    // 合并展示:过滤出过去三个月内的记录,并附加所属分类信息
    const filteredRecent = jsonData.data.filter(
      (item) => new Date(item.created_time) >= threeMonthsAgo
    ).map(item => ({ ...item, category: cat }));
    mergedRecentItems = mergedRecentItems.concat(filteredRecent);
  })
);

// 对合并后的所有数据按创建时间降序排序,并取前20条
mergedRecentItems.sort((a, b) => new Date(b.created_time) - new Date(a.created_time));
mergedRecentItems = mergedRecentItems.slice(0, 20);

// 定义生成跳转链接的函数
const getLink = (entry) => {
  if (entry.category === "movie") {
    // 对于电影,点击跳转到固定的电影 JSON 文件地址
    return jsonUrls.movie;
  } else {
    // 对于其它分类,构造链接为:https://neodb.social/{分类}/{uuid}
    return `https://neodb.social/${entry.category}/${entry.item.uuid}`;
  }
};
---
<Layout title="NeoDB" description="NeoDB">
  <div class="container mx-auto py-8">
    <h1 class="text-3xl font-bold mb-6">最近看/玩了什么</h1>
    <!-- <p class="text-gray-500 mb-6">
      上半部分展示过去3个月内的全部记录,下半部分按分类展示各自最新的20条数据。
    </p> -->
    <!-- 全部展示部分:仅展示过去3个月内的记录 -->
    <section class="mb-8">
      <!-- <h2 class="text-2xl font-semibold mb-4">全部</h2> -->
      {mergedRecentItems && mergedRecentItems.length > 0 ? (
        <div class="grid grid-cols-2 md:grid-cols-5 gap-3">
          {mergedRecentItems.map((entry) => (
            <a href={getLink(entry)} class="block">
              <div class="border rounded overflow-hidden shadow hover:shadow-lg transition-shadow">
                <img
                  src={entry.item.cover_image_url}
                  alt={entry.item.title}
                  loading="lazy"
                  class="w-full h-48 object-cover"
                />
              </div>
              <p class="mt-2 text-sm font-bold text-center truncate">
                {entry.item.title}
              </p>
            </a>
          ))}
        </div>
      ) : (
        <p class="text-gray-500">暂无数据</p>
      )}
    </section>
    <hr class="my-8" />
    <!-- 分类展示部分:各分类显示最新20条数据,不限制时间 -->
    {Object.keys(jsonUrls).map((cat) => (
      <section class="mb-12">
        <h1 class="text-2xl font-semibold mb-4">{categoryMap[cat]}</h1>
        {grouped[cat] && grouped[cat].length > 0 ? (
          <div class="grid grid-cols-2 md:grid-cols-5 gap-3">
            {grouped[cat].map((entry) => (
              <a href={getLink(entry)} class="block">
                <div class="border rounded overflow-hidden shadow hover:shadow-lg transition-shadow">
                  <img
                    src={entry.item.cover_image_url}
                    alt={entry.item.title}
                    loading="lazy"
                    class="w-full h-48 object-cover"
                  />
                </div>
                <p class="mt-2 text-sm font-bold text-center truncate">
                  {entry.item.title}
                </p>
              </a>
            ))}
          </div>
        ) : (
          <p class="text-gray-500">暂无数据</p>
        )}
      </section>
    ))}
  </div>
</Layout>

4.效果展示/写在最后

将数据存储在本地的好处是不用担心日后哪天失效, 倘若失效还有数据可以展示(笑). 如果我有描述不清晰的地方, 请尽情评论, 我会将没讲清楚的地方更新在文中.

1月小结-天大寒, 过年

2025-02-07 07:02:00

魁省进入雪天也有很长一段时间了, 每次回家都被风雪吹得狼狈不堪, 这对一个从小在国内中原地区长大的人还是很有冲击力的... 本月的生活还是比较充实的.

学习

尽管在美国读书的朋友一再劝我选容易通过的简单课程, 但是受制于必修课的选择, 我还是选择了一门对我来说颇有难度的Java编程课. 这门课程每周会有一个实验课需要强制出席, 每次需要解决两道算是LeetCode的easy难度的算法题, 这对大学本科几乎没有动手从0开始写java代码的我很是头大. 不过固定的考查模式让我上手很快, 时至今日的第三次实验课我已经可以全部完成.

生活

生活上简简单单, 我也一贯坚持着什么都能苦, 就是不能苦我的嘴的观点在努力干饭. 我也在我的文字性内容归档站更新了Instantpot食谱. 用来记录一些可以用高压锅省心完成的饭. 由于过年的时候是周二, 我们几个中国留学生朋友都是满课, 于是我们提前到了周末, 聚在一起吃了顿年夜饭, 我也是掏出了逢年必做的烤鸡.

由于经常下雪的缘故, 我也经常开车, 从国内没上过几次高速的新手也变成了现在倒车入库比较在行的"中手"了.

博客更新

这个月算得上是井喷式更新了:

此外我对博客的样式也一直在进行微调,例如将<h1>设置为了渐变的彩色, 为最近做什么和友链单独创建了页面, 这里立一个从2024年传过来的flag: 添加多图拼接功能.

在downtown吃到的让人眼前一亮的寿司