前年清明前的一个深夜,我收到一条很长的消息。发消息的朋友在海外出差,实在赶不回老家扫墓。他说,想给爷爷写封信,但不知道能寄到哪里。我看完那行字,回了一句:“寄到我这里,我给你做成一个网页。” 那个网页最后有了名字:清明纪念·时光信笺。
简单来说,这是一个纯静态的纪念页面:打开链接,先是微弱的烛光或者一片星空,信封慢慢展开,信的正文逐字浮现。页面底部还有一支可以点亮的蜡烛,以及一小块留给来访者写字的区域。它不替代扫墓,不替代任何线下仪式,只做一件事:把一段不敢忘的记忆,稳定地存放在互联网里,让亲友在任何想看看的时候,都能打开。
这个项目的技术门槛不高,很适合前端新手、独立开发者拿来练手,也很适合普通人给家人做一份长期的数字纪念。但“门槛不高”不等于“没有门道”。真正跑一遍之后我发现,最花精力的反而不是代码,而是内容组织、页面节奏、多端兼容还有长期维护。这篇文章就把我从场景、技术选型、核心实现、部署到上线后问题处理的完整过程记录下来,给你一个可以直接照做的参考。
1. 项目从哪来:一次无法回乡的清明
1.1 那一晚的求助消息
朋友的爷爷是一名老教师,退休后在院子里种了十几年的月季,去世前还留了一整箱书给他。朋友在消息里写了一段话,我到现在还记得大概:“爷爷,我把你留下的那本《诗经》带出来了,扉页上你写着1998年7月,比我岁数还大三年。我翻到《王风·黍离》那一篇,有一道铅笔划痕,应该是你留下的。” 他说想把这番话寄回去,可老家的院子已经没人住了,连信都不知道该寄到哪个地址。
我当时就想明白了一件事:数字纪念的核心不是技术,是“让没说完的话,还有地方可以放”。传统扫墓受时间、空间、家庭结构的限制,在外地工作的人越来越多,清明能到现场的次数越来越少。而网页几乎不受这些限制,只要有人维护地址,它就能一直存在。
这个项目最后做成了一个链接。没有app,没有小程序,没有任何需要下载安装的东西。打开就是一个安静的信笺页面。对使用它的人来说,学习成本为零,这才是最关键的产品决策。
1.2 产品形态:一张会开花的信
“时光信笺”的产品形态,一开始就在我脑子里比较清晰了。打开链接后,页面是深蓝色的夜色背景,中央是一封印着火漆的信封。点击信封,翻盖打开,信纸缓缓升起来,正文用打字机效果逐字浮现。信纸上有日期、有称呼、有落款。页面往下滑动,能看见一张老照片,照片下面是“点亮蜡烛”的区域,点完蜡烛后可以在一个文本格里写下“今年我也来看您了”这样的话,内容保存在本机。
这个形态有几个关键决策:
- 全程不需要注册登录,打开即用。
- 所有内容静态存在页面里,不做社交分享、不做评论、不做数据统计。
- 信可以写多封,按日期自动切换,比如清明、除夕、中秋各有各的内容。
- 所有素材离线可用,网络不好也能打开。
为什么这样设计?因为纪念这个场景最忌讳“运营思维”。如果一个纪念页跳出来“扫码关注公众号”,那整个体验就毁了。它是私人的、安静的,所有交互按钮必须异常克制。
1.3 定位边界:不替代仪式,只负责被记住
在动手之前,我想清楚了这个页面“不做什么”。它不做排行榜、不记录谁来看过、不提醒用户“该纪念了”。数字祭扫本身不能替代实体仪式,它只是传统仪式的一个补充。对能回家扫墓的人来说,它是回家后继续回味的一个入口;对实在回不去的人来说,它是一个深夜可以打开的地方。
把这个边界划清楚非常重要,它直接影响后续所有设计。因为一旦想“让更多人看到”,就会加分享裂变、加留言墙、加各种花活,最后做出来的东西不像纪念页,像一个营销活动页。划清边界之后,我做了另一个决定:不追求任何流量指标,页面打开后的每个像素,都只为阅读服务。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型:坚持用纯静态页面的原因
2.1 为什么不用框架,也不用服务器
决定技术栈时,我几乎没有犹豫就选择了纯静态 HTML/CSS/JavaScript,没有构建工具、没有 npm 依赖、没有后端服务。朋友问我是不是太保守了,我说这个项目恰恰必须保守。
第一,稳定性优先。一个纪念页可能要被打开十年、二十年。原生HTML是互联网最基本的存在形式,任何浏览器、任何设备都能解析。但Vue项目十年后还能不能构建,React生态十年后会变成什么样,没人能保证。框架是给工程师用的,而纪念页是给家人用的,家人不需要去npm install。
第二,零成本、零维护。静态文件推到任意对象存储或静态托管平台上就行,没有服务器要续费、没有数据库要备份。最坏的情况下,连域名都不买,直接用平台给的二级域名都能跑。
第三,安全边界小。没有后端就没有注入点,没有数据库就没有泄露风险。静态页面里最多也就是一些文本、一张照片、一段音频,即使被完整下载,也不会造成更恶劣的后果。
当然,这也意味着主动放弃了动态能力。如果要做多人实时留言、照片动态上传,就必须引入服务端。我评估了一下,这些功能对纪念场景反而是一种干扰,所以直接砍掉。
2.2 项目目录与文件清单
这是项目最终的文件结构,非常简单:
text复制time-letter/
├── index.html
├── css/
│ └── style.css
├── js/
│ ├── letter.js // 打字机与信件逻辑
│ ├── candle.js // 星空与蜡烛场景
│ └── main.js // 页面初始化与交互
├── assets/
│ ├── photo.jpg // 逝者照片(可选)
│ ├── audio.mp3 // 氛围音(可选)
│ └── seal.svg // 信封火漆印
├── content/
│ └── letters.json // 信件内容与时间配置
└── README.md
重点说一下 content/letters.json。这个文件把“内容”和“代码”拆开了,非技术用户之后想修改信件内容,直接编辑这个JSON即可,完全不用碰JS逻辑。
json复制{
"family": "爷爷",
"ownerName": "孙儿阿远",
"coverText": "给爷爷的一封信",
"phases": [
{
"date": "04-04",
"title": "清明",
"text": "……正文内容……"
},
{
"date": "12-31",
"title": "除夕",
"text": "……正文内容……"
},
{
"date": "09-17",
"title": "中秋",
"text": "……正文内容……"
}
]
}
date 字段存的是“月-日”格式,这样不需要年份,每年都能复用。如果日期匹配不上,就显示 ownerName 写的一段默认信。这个“一封主信+多封定时信”的结构,是我在跟朋友沟通中逐渐稳定下来的:日常打开是一个版本,每逢节点自动换一封新的,像收信一样有期待感。
2.3 本地运行:一个命令的事
因为是纯静态项目,本地运行方式非常随意。我平时习惯用 Python 自带的服务器:
bash复制cd time-letter
python3 -m http.server 8080
然后浏览器打开 http://localhost:8080 就能运行。如果你没装 Python,也可以用 Node 的 npx serve .,或者 VS Code 装一个 Live Server 插件,右键打开都行。
这里有一个很重要的经验:尽量不要用 file:// 协议直接双击 index.html 打开。因为 letters.json 是通过 fetch() 加载的,浏览器对 file:// 请求有安全限制,直接双击大概率会加载失败。如果把信的内容直接以 JS 对象写死在 main.js 里,倒是可以避免这个问题,但我更建议保持 JSON 分离、用本地服务器运行,因为以后真正部署时逻辑是一致的。
2.4 版本管理:提交一次,然后“冻结”
项目写完我做的第一件事,是 git init 并提交,然后打了一个 tag:
bash复制git init
git add .
git commit -m "time-letter v1.0.0"
git tag v1.0.0
之后我基本不再动主功能代码。纪念页最怕“迭代”:今天换主题,明天加特效。一旦家人习惯了打开后的样子,任何大改都会破坏认知锚点。以后每年清明节只需做内容更新,改 letters.json 里的文字、换一两张照片,然后发一个小版本即可。这与商业项目完全相反,属于“做得越少越好”的项目。
3. 写给逝者的信:真正花时间的是内容而非代码
3.1 怎么帮非技术用户起草内容
项目代码我大概两个晚上就写完了,但帮朋友整理信的内容,花了一周多。这让我明白,这类页面的决胜点根本不在技术,在文字。
跟朋友来回改稿的过程中,我总结出一个方法:不要一开始就要求对方“写一篇好文章”,那样压力太大,写出来也很容易空。正确顺序是:
- 先让对方打开手机录音,把想说的话口述一遍,想到哪说到哪。
- 把录音转成文字草稿。
- 草稿里找出有“具体时间、具体地点、具体物品”的句子,这些是灵魂。
- 去掉大段的情绪形容词,比如“非常非常想念”,这些词留给读者自己脑补。
- 按“称呼 → 具体往事 → 家里现在的变化 → 一句祝愿 → 落款日期”的顺序重新排列。
朋友最初的稿件里写了很多“想念”“对不起”“我永远记得你”,但这几句在我读来都不如他口述时提到的那本旧《诗经》有力量。最后我们把信改造成三个层次:先写小时候爷爷带着念诗;再写翻到书里铅笔画线时的震动;最后写今年家里月季又开了。克制,但每一句都有画面。
3.2 多时间段信笺自动切换
当初设计自动切换,是因为朋友提到一个细节:“去年中秋我在国外,晚上看到月亮,突然想爷爷了,但什么也做不了。” 于是我们把固定的一封信改成多封,让不同日子打开页面时,会看到不同的信。
JS判断逻辑不复杂:
javascript复制function getPhaseForToday(phases) {
const today = new Date();
const now = `${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`;
return phases.find(p => p.date === now) || phases[0];
}
phases[0] 作为默认信,保证任何日期打开都有内容可看。如果你希望某个时间范围内显示同一封信,可以进一步扩展 startDate 和 endDate 字段,这里不再展开。
这个功能给页面带来的价值是:它从一个静态页面变成了一个有“时间的”页面。家人一年里打开几次,有可能看到不同的内容,这种惊喜感本身也构成再次访问的理由。
3.3 阅读节奏与排版原则
信的内容有了,怎么呈现就是关键。纪念页不是小说,不宜一上来就铺出一整屏密匝匝的文字。我的处理方式:
- 一次性只显示 3 到 5 段。
- 逐字浮现,但整体不能太慢。中文每字 45 到 70 毫秒,标点符号额外停顿。一封信 300 字,打字机播完大约在 30 秒内,这个节奏既保留仪式感,又不至于让阅读者不耐烦。
- 移动端正文不小于 16px,行高 1.9。
- 段与段之间用空行分隔,不首行缩进,因为首行缩进在窄屏上容易造成排版不齐。
排版上还有一个容易被忽略的细节:如果文字量较大,要给页面底部预留至少 40% 的空白,避免阅读到底时手指无处安放。整个页面设计得像一张真实的信纸,而不是一个信息流。
4. 页面核心功能实现:信封、打字机与烛光
4.1 信封开启动画
信封是首页最核心的视觉元素。我用三层 div 模拟:信封背面、信纸、翻盖。点击翻盖后,给翻盖加一个 transform: rotateX(180deg) 的过渡,同时信纸从下方 translateY(-60px) 升起。
html复制<div class="envelope" id="envelope">
<div class="envelope__back"></div>
<div class="envelope__letter">
<p>点击展开</p>
</div>
<div class="envelope__flap"></div>
</div>
CSS 的核心部分:
css复制.envelope__flap {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform-origin: top center;
transition: transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
z-index: 3;
}
.envelope.open .envelope__flap {
transform: rotateX(180deg);
z-index: 1;
}
.envelope.open .envelope__letter {
transform: translateY(-60px);
transition: transform 0.6s ease 0.3s;
}
动画要做得像真实信封,关键在过渡时间曲线和延迟。翻盖转起来大约 0.8 秒,信纸要等翻盖转到一半才开始上浮,所以延迟 0.3 秒。这个节奏我是反复调整后定下来的,太快没有仪式感,太慢又让人着急。
4.2 中文打字机效果
打字机效果我刚开始写了一个无脑的 setInterval,后来发现中文标点停顿不合理:逗号和句号之间没有区别,整封信读起来像机关枪。改进后的逻辑是每个字符根据类型返回不同延迟:
javascript复制function getDelay(ch) {
if (/[。!?…]/.test(ch)) return 350;
if (/[,、;:]/.test(ch)) return 200;
if (/[,.!?;:\s]/.test(ch)) return 120;
return 55;
}
主循环用一个递归 setTimeout,而不是 setInterval,因为每次延迟不同:
javascript复制function typeText(el, text, speed = 55) {
return new Promise((resolve) => {
let index = 0;
function tick() {
el.textContent = text.slice(0, index + 1);
index++;
if (index >= text.length) {
resolve();
return;
}
const ch = text[index];
const delay = speed + getDelay(ch);
setTimeout(tick, delay);
}
tick();
});
}
另外一个细节:代码里检测到用户开启了 prefers-reduced-motion 时,应该直接显示全文,跳过打字机效果。这对阅读障碍用户友好,也是一种更负责任的做法。
4.3 星空与烛光场景
背景场景我最终实现了两个:星空和烛光。星空用 Canvas 画几百颗粒子,烛光用 CSS 渐变加动画。有人会问为什么不用现成的粒子库,因为这么简单的效果根本不需要引入依赖。第三方库一旦停止维护、出现兼容性问题,反而增加长期风险。
Canvas 星空的简化逻辑:
javascript复制class StarField {
constructor(canvas, starCount = 80) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.stars = Array.from({ length: starCount }, () => ({
x: Math.random(),
y: Math.random() * 0.7,
r: Math.random() * 1.2 + 0.3,
phase: Math.random() * Math.PI * 2
}));
this.t = 0;
}
draw() {
const { ctx } = this;
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
ctx.fillStyle = '#0b1020';
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.stars.forEach(star => {
const alpha = 0.4 + 0.6 * Math.sin(this.t * 0.02 + star.phase) ** 2;
ctx.globalAlpha = alpha;
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(star.x * this.canvas.width, star.y * this.canvas.height, star.r, 0, Math.PI * 2);
ctx.fill();
});
this.t++;
requestAnimationFrame(() => this.draw());
}
}
星空粒子数量在移动端要降低。我在实际测试中把默认值调成 80,在低端安卓机上仍然流畅,但如果你的页面还有别的动画,建议在检测到性能不佳时直接关闭 Canvas,改用纯 CSS 背景。
烛光则比较取巧:一个圆形渐变模拟光源,再用 transform: scaleY() 加 filter: brightness() 循环动画模拟火光抖动。写起来非常轻,视觉效果却已经足够。
4.4 记录每一次“来看望”
页面底部留了一个“点亮蜡烛”的区域,逻辑很简单:点击后蜡烛点亮,同时把今年是否点亮的标记记录在 localStorage 里,明年再打开时会重新询问。
javascript复制const KEY = 'letter_candle_' + new Date().getFullYear();
const lit = localStorage.getItem(KEY) === '1';
if (lit) {
candle.classList.add('lit');
}
btn.addEventListener('click', () => {
candle.classList.add('lit');
localStorage.setItem(KEY, '1');
});
为什么不把“今年点亮了”传到服务器?因为这个项目刻意不用服务器,而且从隐私角度看,某个人是否来祭奠不应当被平台记录和披露。本地记录已经足够:它只对当前这台设备上的使用者负责,来访者自己心里知道就行。
5. 兼容性与性能控制:家人手里的手机才是主战场
5.1 微信内置浏览器的三个坑
项目部署后,我发现家人打开页面的主要入口是微信。微信内置浏览器和标准浏览器不完全一致,有三个坑必须提前处理。
第一个是音频自动播放限制。几乎所有移动端浏览器都禁止页面一打开就自动出声,微信更严格。处理方案:不要在页面加载时调用 audio.play(),必须等到用户至少完成一次点击后,再去触发播放。在这个项目里,“点击信封”这个动作已经构成一次用户手势,所以把音频的 play() 放进信封点击事件里即可。
第二个是字号被强制放大。微信内置浏览器会“智能识别”中文字号太小的页面并强制调整字体大小,结果就是排版变形。只要在 CSS 里加一行就可以避免:
css复制html {
-webkit-text-size-adjust: 100%;
}
第三个是分享卡片预览问题。微信分享给好友时,如果没有任何配置,卡片只有灰色空白。至少要在 HTML 头部加 Open Graph 标签,并准备一张 600x600 以上的封面图:
html复制<meta property="og:title" content="时光信笺" />
<meta property="og:description" content="一封写给爷爷的信" />
<meta property="og:image" content="https://your-domain.com/assets/cover.jpg" />
<meta property="og:type" content="website" />
还要注意,图片链接必须是完整的绝对地址,不能写相对路径,否则微信无法抓取。
5.2 字体、图片、音频的体积控制
纪念页通常都会放逝者照片和一段安静的音乐。这些素材的体积直接决定页面打开速度,而“等待时间”是纪念场景里最破坏情绪的东西。
照片的处理原则:最多两张,一张作为信纸背景,一张作为相册页。每张都压缩到 200KB 以内,用 JPEG 格式就足够。老照片如果有明显噪点,可以适当降噪,但我不建议过度使用 AI 修复,过度修复会让照片失去原本的表情和质感。
中文字体是最大的体积陷阱。一套完整的中文字体动辄几 MB,千万不能为了效果直接整个引进。最稳妥的方案是系统字体栈:
css复制font-family: -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
如果一定想要手写感,可以把信件的标题手写后用 SVG 路径内联到 HTML 中,正文继续用系统字体,这样既保留了辨识度,又不会拖慢速度。
音频建议用不超过 30 秒的循环片段,128kbps 的 MP3,500KB 以内。网上能找到很多免费的 ambient 音乐,注意看清授权协议。
5.3 帧率与耗电处理
星空 Canvas 在桌面浏览器上非常流畅,但在部分安卓手机上连续跑半小时会发热。我的处理策略比较务实:
- 监听
visibilitychange,页面切到后台时暂停动画。 - 检测到
navigator.hardwareConcurrency <= 4时,把星星数量降到 40,并降低绘制分辨率。 - 对高 DPI 屏,将 Canvas 的绘制像素比限制在 2 以内,避免渲染上万级别的冗余像素。
javascript复制const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = canvas.offsetWidth * dpr;
canvas.height = canvas.offsetHeight * dpr;
ctx.scale(dpr, dpr);
如果用户设备开启了“省电模式”或者 prefers-reduced-data,我干脆引导到“简洁模式”,页面变成深色纯背景加一封信,不带任何粒子动画。这个模式在低版本手机上表现更好,也避免了在严肃场合下出现花哨效果。
6. 部署与长期保存:让一封信活过十年
6.1 静态托管的三个选择
我做了对比后,把可选方案缩小到三个:GitHub Pages、Cloudflare Pages、国内云厂商的对象存储加 CDN。
| 方案 | 免费额度 | 是否需要域名备案 | 访问速度参考 | 适合场景 |
|---|---|---|---|---|
| GitHub Pages | 完全免费 | 不需要 | 国内访问视网络情况而定 | 个人保存、技术分享 |
| Cloudflare Pages | 完全免费 | 不需要 | 全球 CDN,国内速度往往比 GitHub Pages 好些 | 兼顾国内家人访问 |
| 国内对象存储 + CDN | 一般有免费流量额度 | 使用内地节点必须备案 | 速度快、稳定 | 家人都在国内或追求最稳访问 |
如果你只是给自己做一个安静的纪念页,GitHub Pages 完全够用。如果要把链接发给老家亲戚,建议至少用 Cloudflare Pages,或者直接走国内对象存储加已备案域名。
如果使用国内对象存储并绑定已备案域名,访问速度是最稳的。但是备案需要配合服务器资源,且周期通常有两到三周,提前规划。备案的具体操作这里不展开,各地区政策有差异,以服务商指引为准。
6.2 域名与访问验证
域名我会建议单独买一个,不要只依赖平台分配的二级地址。原因是纪念页的地址应该足够简单,简单到家人能直接口口相传:假如域名是一个长串的随机字符,你真没信心让老人记住。
购买域名时注意这几件事:
- 域名最好包含写名字的拼音、姓氏拼音或者缩写,方便识别。
- 开启自动续费,并且在日历里设置到期提醒,比平台默认的邮件提醒更可靠。
- 不要买过于猎奇的新顶级域,.com / .net / .cn 这些主流后缀兼容性最好。
我自己的实践是每年清明前检查一次:域名是否正常续费、证书是否过期、页面能否打开、备份是否完整。整个过程大概十分钟,放在手机上也能完成。
6.3 本地备份与传承
线上平台有倒闭或跑路风险,所以本地备份绝不能省。我做了一次完整备份,并把备份做成了三层:
- 在 GitHub 建一个私有仓库,保存全部源码和素材,每次内容更新后
commit一次。 - 打包一个 zip 压缩包,放到 U 盘里,收进家里的抽屉。
- 用一张普通的 A4 纸,写下三样东西:页面链接、域名到期时间、备份 U 盘位置。这张纸随 U 盘一起收好,也算一份“数字遗产说明”。
有人会觉得一张纸太原始,但真实生活中,账号密码比代码更容易失传。U 盘和纸虽然笨,却是最不依赖任何平台的方式。几年后如果我不在了,家人依然可以找到这个页面,或者基于备份自行重建。
6.4 冻结项目的原则
项目上线后,我给自己定了一条规矩:核心功能不做大改。版式、色调、动效争取保持原样。每年只允许做两件事:更新 letters.json 里的文字;更换当年的照片。如果修复了严重的兼容性问题,会用小版本号发布,并在 README 里记录变更。
这样做的本质是在维持“记忆的稳定性”。人对于旧物的情感,很大程度来源于“它没变”。页面如果每次打开都大变样,它就不再是信笺,而是周年庆营销页面了。
7. 上线后的意外清单:踩过的坑和对应的调整
7.1 链接太长发不出去
第一次分享链接的时候,我发现 GitHub Pages 分配到的地址很长,有些人复制不完整。后来我绑定了一个简短域名,问题立刻解决。如果你不想买域名,也可以把链接转成二维码,让家人用手机相机扫码打开,这是更直接的规避方式。
7.2 手机字号被强制放大
这是家人在安卓手机上反馈的典型问题。明明 PC 浏览器一切正常,微信里打开后标题和正文全部变大,段落换行变得乱七八糟。原因就是我上面提到的 -webkit-text-size-adjust 没有设置。加一行 CSS 后,问题彻底消失。
7.3 微信缩略图一片灰
朋友第一次把链接转发到家庭群,微信卡片只显示灰色背景,没有照片也没有标题。排查后发现是 og:image 指向了相对路径。微信的抓取器无法识别相对路径,必须用完整的绝对 URL。改成全路径之后,封面正常。另外,如果想让封面有更多控制权,需要接入微信 JS-SDK 的分享接口,但那个流程较重,对普通纪念页来说,做好 OG 标签已经够了。
7.4 家人说“怎么没有声音”
一个亲戚反馈:“页面很温馨,就是没声音。”我确认之后发现,他是在 iOS 上打开了页面但没有点击信封,音频自然没有播放。后来我在信封下方加了一行小字提示:“请点击信封,会有微弱的背景声。”这既给了引导,又不破坏页面氛围。同时在页面侧边放了独立的声音开关,让不想再等信封动画的用户可以直接打开声音。
7.5 部署子路径后 404
如果你把项目部署在类似 https://example.com/time-letter/ 这样的子路径下,资源引用用绝对路径会直接 404。我的解决方式是把资源路径全部改成相对引用,或者给 HTML 加 <base> 标签。我的建议是直接先用相对路径,最简单,也最不容易出错。
做这个项目最大的收获,倒不是代码本身。它逼着我去思考一些平时不怎么想的问题:什么东西能稳定保存十年?什么样的表达能让人在深夜打开时,不觉得被技术打扰?每一次有人问我“想给家人做个纪念页但不知道从哪开始”,我都会说:先从一封最短的信开始写。不用想着做得多好,把第一行写下来,剩下的自然就有了方向。这个页面后来也从朋友爷爷的一封信,变成了一个模板,帮另外两位朋友做给了他们家人。只要有人还在点开它,它就一直活着。
