先看下效果。

之前总觉得 iPhone 锁屏界面中间这部分区域不能自定义一些文字有点遗憾。倘若能放一些重要的提醒或者运动统计信息,可以更好地提醒自己、督促自己运动。毕竟手机一点亮就可以看到。
之前也问过 AI,都没有得到什么好的方案。
直到有一天,同事发给我这个网站。
https://lifegrid-wallpapers.pages.dev/
受它的启发,一通研究,最后实现我想要的效果。真是念念不忘,必有回响。
大致流程如下。

下面是实现过程。
Notion 里记录运动
快捷输入,参照 Notion+Scriptable+快捷指令,每天完成最重要的事
Mac mini 定期获取运动历史记录,并上传到云服务器
55 23 * * * /bin/bash /Users/jinhuaiyao/Nextcloud/Config/Mac_Script/upload_heatmap_data.sh >/Users/jinhuaiyao/Log/upload_heatmap_data.txt 2>&1
(调用 notion api 获取特定的数据,上传到云服务器)
在原有的 job上 添加三段。
jinhuaiyao@huaiyaos-mac-mini ~ % cat /Users/jinhuaiyao/Nextcloud/Config/Mac_Script/upload_heatmap_data.sh
cd /Users/jinhuaiyao/.tmp/
DT=`date +%Y_%m_%d`
for file in `ls *.txt`
do
cp $file /Users/jinhuaiyao/Nextcloud/Backup/backup_heatmap/${file}.${DT}
done
bash workout.sh
bash running.sh
bash reading.sh
bash vocabulary.sh
bash englishpod.sh
bash plank.sh
cat workout_2026.txt | awk -F'跑步|公里' '/跑步/ {sum += $2; count++} END {printf "跑步总次数: %d 次\n跑步总长度: %.1f 公里\n", count, sum}' >workout_2026_sum.txt
cat workout_2026.txt | awk -F'跳绳|个' '/跳绳/ {sum += $2; count++} END {printf "跳绳总次数: %d 次\n跳绳总个数: %.f 个\n", count, sum}' >>workout_2026_sum.txt
awk -F'|' '{
count++;
t = $3; gsub(/ /, "", t);
m = 0; s = 0;
if (t ~ /分/ && t ~ /秒/) {
split(t, a, "分"); m = a[1]; sub(/秒/, "", a[2]); s = a[2];
} else if (t ~ /分钟/) {
sub(/分钟/, "", t); m = t;
} else if (t ~ /分/) {
sub(/分/, "", t); m = t;
}
total = m * 60 + s;
if (total > max) { max = total; max_str = $3 }
}
END {
printf "平板支撑总次数: %d 次\n平板支撑最长时间: %s\n", count, max_str
}' plank_2026.txt >>workout_2026_sum.txt
scp -P 10086 *.txt [email protected]:/home/xx/www/webpage
cd /Users/jinhuaiyao/Nextcloud/Backup/backup_heatmap/
find . -type f -mtime +20 -exec rm {} \;
三个 txt 文件的格式如下:
jinhuaiyao@huaiyaos-mac-mini .tmp % tail -5 workout_2026.txt
2026-02-12 |1 | 跳绳800个
2026-02-13 |1 | 跳绳800个
2026-02-14 |1 | 跳绳400个
2026-02-20 |1 | 跳绳1200个
2026-02-21 |1 | 跳绳1500个
jinhuaiyao@huaiyaos-mac-mini .tmp % tail -5 plank_2026.txt
2026-02-10 |1 | 1分45秒
2026-02-12 |1 | 1分30秒
2026-02-19 |1 | 1分45秒
2026-02-20 |1 | 1分15秒
2026-02-21 |1 | 3分钟
jinhuaiyao@huaiyaos-mac-mini .tmp % tail workout_2026_sum.txt
跑步总次数: 9 次
跑步总长度: 58.3 公里
跳绳总次数: 12 次
跳绳总个数: 13000 个
平板支撑总次数: 27 次
平板支撑最长时间: 3分钟
云服务器定期运行脚本生成带有统计信息的壁纸
# cron job
25 * * * * /root/wallpaper_env/bin/python3 /root/wallpaper_env/generate_wallpaper.py >/root/generate_wallpaper.log 2>&1
root@meta-unicorn-2:~# cat /root/wallpaper_env/generate_wallpaper.py
import requests
from PIL import Image, ImageDraw, ImageFont
import re
import os
# --- 配置区 ---
DATA_URL = "https://xx.xx.com/workout_2026_sum.txt"
BASE_IMAGE_PATH = "/root/wallpaper_env/base_wallpaper.jpg"
OUTPUT_PATH = "/home/xx/www/webpage/final_wallpaper.png"
WIDTH, HEIGHT = 1260, 2736
# 字体路径
FONT_PATH = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"
if not os.path.exists(FONT_PATH):
FONT_PATH = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
def get_workout_data():
try:
r = requests.get(DATA_URL, timeout=10)
r.encoding = 'utf-8'
text = r.text
return {
"dist": re.search(r"跑步总长度:\s*([\d.]+)", text).group(1),
"run_count": re.search(r"跑步总次数:\s*(\d+)", text).group(1),
"rope_count": re.findall(r"跳绳总次数:\s*(\d+)", text)[0],
"rope_num": re.search(r"跳绳总个数:\s*(\d+)", text).group(1),
"plank_count": re.search(r"平板支撑总次数:\s*(\d+)", text).group(1),
"plank_time": re.search(r"平板支撑最长时间:\s*(.+)", text).group(1).strip()
}
except Exception as e:
print(f"Data parse error: {e}")
return None
def generate_wallpaper():
data = get_workout_data()
if not data: return
try:
base_img = Image.open(BASE_IMAGE_PATH).convert('RGB')
img = base_img.resize((WIDTH, HEIGHT), Image.Resampling.LANCZOS)
except Exception as e:
print(f"Image error: {e}")
return
draw = ImageDraw.Draw(img)
# 1. 字体与颜色
f_xl = ImageFont.truetype(FONT_PATH, 100)
f_md = ImageFont.truetype(FONT_PATH, 40)
f_sm = ImageFont.truetype(FONT_PATH, 40)
C_BLUE = "#68abe7"
C_WHITE = "#FFFFFF"
# 2. 布局基准
X_POS = WIDTH * 0.12
Y_START = HEIGHT * 0.50
SECTION_GAP = 270
def draw_data_block(start_y, label, val, unit):
draw.text((X_POS, start_y), label, fill=C_BLUE, font=f_sm)
draw.text((X_POS, start_y + 60), val, fill=C_WHITE, font=f_xl)
val_w = f_xl.getlength(val)
draw.text((X_POS + val_w + 20, start_y + 125), unit, fill=C_BLUE, font=f_md)
def draw_separator(y_pos):
draw.line([(X_POS, y_pos), (X_POS + 200, y_pos)], fill=C_BLUE, width=3)
# --- 开始绘制 ---
curr_y = Y_START
# 1. 跑步
draw_data_block(curr_y, f"跑步 - {data['run_count']} 次", data['dist'], "公里")
draw_separator(curr_y + 240)
# 2. 跳绳
curr_y += SECTION_GAP
draw_data_block(curr_y, f"跳绳 - {data['rope_count']} 次", data['rope_num'], "个")
draw_separator(curr_y + 240)
# 3. 平板支撑
curr_y += SECTION_GAP
draw_data_block(curr_y, f"平板支撑 - {data['plank_count']} 次", data['plank_time'], "最长")
# 垂直线
draw.line([(X_POS - 45, Y_START), (X_POS - 45, curr_y + 190)], fill=C_BLUE, width=7)
img.save(OUTPUT_PATH, "PNG")
print(f"Successfully generated: {OUTPUT_PATH}")
if __name__ == "__main__":
generate_wallpaper()
大致过程是:
先在网上下载一个适合手机分辨率的原有壁纸(每个机型会不一样),放到
/root/wallpaper_env/base_wallpaper.jpg
Python 脚本读取
/home/xx/www/webpage/workout_2026_sum.txt
的内容,将内容绘制到 base_wallpaper.jpg 上,生成带有文字的新的壁纸。
/home/xx/www/webpage/final_wallpaper.png
这个文件可以通过以下 URL 访问到。
https://xx.xx.com/final_wallpaper.png
iPhone快捷指令每天定时更新壁纸
设计一个快捷指令,并设置自动化每天凌晨运行一次。
完工!