fix: 修复首页空白、小红书/游记封面图、定制页二维码

1. 首页大面积空白:useScrollReveal 添加 MutationObserver,
   确保异步子组件的 .reveal 元素也能触发入场动画

2. 小红书/游记封面图不显示:
   - 小红书 CDN 签名链接过期+防盗链,全部下载到本地
   - 新增 download-covers.mjs 脚本批量下载封面
   - 新增 useImageProxy composable 转换外部 URL
   - 页面 img 标签添加 referrerpolicy="no-referrer"
   - 管理端保存时自动下载外部封面图到本地
   - 种子数据更新为本地路径

3. 定制页微信号文字替换为二维码图片

4. prerender 路由添加 /xiaohongshu 和 /youji

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
这个提交包含在:
刘涛 2026-03-25 15:37:58 +08:00
父节点 b82712deed
当前提交 8832762e51
共有 16 个文件被更改,包括 599 次插入106 次删除

查看文件

@ -1,7 +1,7 @@
<template>
<div class="image-upload">
<div class="upload-preview" v-if="modelValue">
<img :src="modelValue" alt="预览" @error="imgError = true" />
<img :src="modelValue" alt="预览" referrerpolicy="no-referrer" @error="imgError = true" />
<div class="preview-actions">
<button type="button" class="btn-icon" @click="$emit('update:modelValue', '')" title="移除">&#10005;</button>
</div>

查看文件

@ -0,0 +1,38 @@
/**
* 小红书图片 URL 转换
*
* 小红书 CDN 图片 (sns-webpic-qc.xhscdn.com) 使用签名 URL
* 格式: https://sns-webpic-qc.xhscdn.com/{timestamp}/{hash}/{path}
* 这些 URL 会过期且有防盗链第三方站点无法直接使用
*
* 解决方案转换为 ci.xiaohongshu.com 的永久 URL
* 格式: https://ci.xiaohongshu.com/{path}
*/
/**
* 将小红书签名 URL 转换为永久可访问的 URL
*/
export function proxyImage(url) {
if (!url) return ''
try {
const parsed = new URL(url)
// sns-webpic-qc.xhscdn.com/{timestamp}/{hash}/{actual_path}
// 提取 actual_path 部分,转到 ci.xiaohongshu.com
if (parsed.hostname === 'sns-webpic-qc.xhscdn.com') {
const parts = parsed.pathname.split('/')
// 去掉前导空字符串、timestamp、hash,剩下的就是图片路径
// pathname: /202603231146/hash/notes_pre_post/image_id
if (parts.length >= 4) {
const imagePath = parts.slice(3).join('/')
return `https://ci.xiaohongshu.com/${imagePath}`
}
}
// 已经是 ci.xiaohongshu.com 的永久链接,直接用
if (parsed.hostname === 'ci.xiaohongshu.com') {
return url
}
} catch {
// 不是合法 URL可能是本地路径,直接返回
}
return url
}

查看文件

@ -2,28 +2,44 @@
* 滚动入场动画 composable
* 元素进入视口时触发淡入上滑动画
* 自动尊重 prefers-reduced-motion
* 使用 MutationObserver 确保异步渲染的子组件也能被观察到
*/
export default function useScrollReveal() {
onMounted(() => {
// 尊重用户减少动效偏好
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
const observer = new IntersectionObserver(
const io = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible')
observer.unobserve(entry.target)
io.unobserve(entry.target)
}
})
},
{ threshold: 0.1, rootMargin: '0px 0px -40px 0px' }
)
document.querySelectorAll('.reveal').forEach((el) => {
observer.observe(el)
})
onUnmounted(() => observer.disconnect())
// 观察当前已有的 .reveal 元素
const observed = new WeakSet()
function observeAll() {
document.querySelectorAll('.reveal:not(.is-visible)').forEach((el) => {
if (!observed.has(el)) {
observed.add(el)
io.observe(el)
}
})
}
observeAll()
// 监听 DOM 变化,捕获异步渲染的子组件中的 .reveal 元素
const mo = new MutationObserver(observeAll)
mo.observe(document.body, { childList: true, subtree: true })
onUnmounted(() => {
io.disconnect()
mo.disconnect()
})
})
}

查看文件

@ -11,341 +11,434 @@
{
"id": "68e4cd12000000000401538b",
"title": "呼籁给了美好的呼伦贝尔南线之旅!",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68e4cd12000000000401538b.webp",
"author": "我是一个荷包蛋",
"likes": 4,
"url": "https://www.xiaohongshu.com/explore/68e4cd12000000000401538b",
"tags": ["呼伦贝尔", "南线"],
"tags": [
"呼伦贝尔",
"南线"
],
"ratio": "vertical",
"summary": "选了呼籁的南线行程,师傅特别热情,一路讲解各种草原文化。海拉尔站接我们就感受到了内蒙人的热情,孩子特别开心,还体验了蒙古包住宿,晚上看星星太美了。整个行程安排得很紧凑但不赶,景点都很值得去,强烈推荐呼籁!"
},
{
"id": "697212bd000000000e03da86",
"title": "我在呼伦贝尔拍到了极光",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-697212bd000000000e03da86.webp",
"author": "Mario",
"likes": 228,
"url": "https://www.xiaohongshu.com/explore/697212bd000000000e03da86",
"tags": ["极光", "呼伦贝尔"],
"tags": [
"极光",
"呼伦贝尔"
],
"ratio": "vertical",
"summary": "没想到在呼伦贝尔居然拍到了极光!冬天来的呼籁冰雪线路,本来只是想看雪景,结果师傅说今晚天气好可能有极光,带我们到了一个没有光污染的地方等候。真的等到了!人生第一次看极光,在祖国的土地上,太感动了。呼籁的师傅太专业了,不只是开车,是真的懂这片土地。"
},
{
"id": "68443760000000002100e004",
"title": "幼儿园毕业游🌸呼伦贝尔大草原🌲",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68443760000000002100e004.webp",
"author": "wendy51930",
"likes": 144,
"url": "https://www.xiaohongshu.com/explore/68443760000000002100e004",
"tags": ["亲子游", "毕业游"],
"tags": [
"亲子游",
"毕业游"
],
"ratio": "horizontal",
"summary": "幼儿园毕业送给孩子一个大草原之旅!选择了呼籁的亲子线路,一家一车很自由。孩子骑了小马、挤了牛奶、学了射箭,每天都不想回酒店。领队还给孩子们准备了草原寻宝游戏,太贴心了。住的蒙古包也超级干净,推荐给有娃家庭!"
},
{
"id": "686289e00000000013012fb0",
"title": "一家三口亲子游|呼伦贝尔大草原第二天",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-686289e00000000013012fb0.webp",
"author": "玄猫猫",
"likes": 42,
"url": "https://www.xiaohongshu.com/explore/686289e00000000013012fb0",
"tags": ["亲子游", "草原"],
"tags": [
"亲子游",
"草原"
],
"ratio": "vertical",
"summary": "第二天的行程从额尔古纳出发,穿过白桦林到了莫尔道嘎。一路上风景美炸了,师傅会在好看的地方停下来让我们拍照。孩子在草地上撒欢跑,追蝴蝶、摘野花,城市里根本没有这样的体验。呼籁的安排真的很用心,午餐还带我们去了当地牧民家吃手把肉。"
},
{
"id": "687284f00000000010026e19",
"title": "呼籁的旅拍不错呦",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-687284f00000000010026e19.webp",
"author": "Vivianus爱Matcha是抹茶🔥",
"likes": 5,
"url": "https://www.xiaohongshu.com/explore/687284f00000000010026e19",
"tags": ["旅拍", "呼伦贝尔"],
"tags": [
"旅拍",
"呼伦贝尔"
],
"ratio": "vertical",
"summary": "呼籁安排的旅拍摄影师太会拍了!选了几个小众取景点,避开了人群,拍出来的照片像大片一样。草原上穿蒙古袍的照片直接拿来当头像了。而且不赶时间,慢慢拍,很舒服。价格也比外面单独约摄影师便宜,推荐!"
},
{
"id": "686b2dd800000000150238f8",
"title": "呼伦贝尔~完美收官啦",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-686b2dd800000000150238f8.webp",
"author": "三人四季行",
"likes": 12,
"url": "https://www.xiaohongshu.com/explore/686b2dd800000000150238f8",
"tags": ["收官", "亲子"],
"tags": [
"收官",
"亲子"
],
"ratio": "horizontal",
"summary": "五天四晚的呼伦贝尔之旅完美收官!从海拉尔出发,经过莫日格勒河、额尔古纳湿地、黑山头、满洲里,最后回到海拉尔。全程呼籁安排的师傅服务超好,车子很干净,每天还给孩子准备零食和水。这是我们一家三口最开心的一次旅行!"
},
{
"id": "68987620000000002302987f",
"title": "开启呼伦贝尔草原之旅#呼籁旅行#",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68987620000000002302987f.webp",
"author": "Mo都时髦Mo",
"likes": 8,
"url": "https://www.xiaohongshu.com/explore/68987620000000002302987f",
"tags": ["草原", "旅行"],
"tags": [
"草原",
"旅行"
],
"ratio": "vertical",
"summary": "期待已久的草原之旅终于开启!第一天从海拉尔出发,看到莫日格勒河的那一刻,被震撼到了。天下第一曲水名不虚传!呼籁的定制师提前帮我们规划好了每天的行程,还根据天气调整了顺序,非常灵活。期待接下来几天的精彩!"
},
{
"id": "68a9a205000000001d00b03f",
"title": "6月底的呼伦贝尔包车未被黑",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68a9a205000000001d00b03f.webp",
"author": "婷",
"likes": 22,
"url": "https://www.xiaohongshu.com/explore/68a9a205000000001d00b03f",
"tags": ["包车", "避坑"],
"tags": [
"包车",
"避坑"
],
"ratio": "horizontal",
"summary": "做了很多功课才选定的呼籁,事实证明选对了6月底旺季出行,价格透明没有隐形消费,师傅全程没有带去任何购物点。吃住都是提前确认好的,价格合理品质也好。最重要的是师傅人特别好,不会催赶行程,随时可以停车拍照。呼伦贝尔包车选呼籁不会踩坑"
},
{
"id": "68669f3f000000002201ee0f",
"title": "呼伦贝尔亲子游",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68669f3f000000002201ee0f.webp",
"author": "暴躁妈带俩娃",
"likes": 28,
"url": "https://www.xiaohongshu.com/explore/68669f3f000000002201ee0f",
"tags": ["亲子游", "呼伦贝尔"],
"tags": [
"亲子游",
"呼伦贝尔"
],
"ratio": "vertical",
"summary": "带俩娃出门最怕的就是行程太赶,但呼籁安排得很合理!每天上午出发,中午有充足的午休时间,下午玩到五六点就回酒店了。两个孩子全程没哭闹过,说明行程节奏把控得好。师傅还特别有耐心,会等孩子们慢慢玩够了再出发。"
},
{
"id": "688644e600000000130134b4",
"title": "呼伦贝尔我到底该选谁🤨",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-688644e600000000130134b4.webp",
"author": "扣子挺好的",
"likes": 10,
"url": "https://www.xiaohongshu.com/explore/688644e600000000130134b4",
"tags": ["攻略", "选择"],
"tags": [
"攻略",
"选择"
],
"ratio": "vertical",
"summary": "对比了好几家呼伦贝尔的旅行社,最后选了呼籁。原因很简单:一是客服回复很专业,不是模板回复而是真的在了解我的需求;二是价格透明,把每一项都列清楚了;三是看了很多客户的真实反馈都很好。事实证明没选错,服务确实超出预期!"
},
{
"id": "686beceb000000001703267f",
"title": "呼伦贝尔游记D1",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-686beceb000000001703267f.webp",
"author": "桐桐",
"likes": 5,
"url": "https://www.xiaohongshu.com/explore/686beceb000000001703267f",
"tags": ["游记", "Day1"],
"tags": [
"游记",
"Day1"
],
"ratio": "horizontal",
"summary": "呼伦贝尔之旅第一天!海拉尔机场接上后直奔莫日格勒河。一路上师傅给我们科普了很多当地的风土人情,原来草原上的路也有这么多讲究。莫日格勒河的落日太美了,师傅特意等到日落才带我们走,这种服务态度真的绝了。"
},
{
"id": "69bfc4d0000000002301c126",
"title": "📸26暑假呼伦贝尔亲子游 | 超强攻略‼️",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-69bfc4d0000000002301c126.webp",
"author": "Mr.小雷-呼籁旅行",
"likes": 8,
"url": "https://www.xiaohongshu.com/explore/69bfc4d0000000002301c126",
"tags": ["暑假", "攻略"],
"tags": [
"暑假",
"攻略"
],
"ratio": "vertical",
"summary": "2026年暑假呼伦贝尔亲子游超全攻略来了包含最佳出行时间、行程推荐、必玩体验、穿搭建议和避坑指南。7-8月是草原最美的季节,建议提前1-2个月预订。亲子游推荐5-6天的行程,不赶路不疲惫,孩子和大人都能玩得尽兴。"
},
{
"id": "68a2f57d000000001c03e65f",
"title": "呼伦贝尔亲子游",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68a2f57d000000001c03e65f.webp",
"author": "野象小姐YY",
"likes": 3,
"url": "https://www.xiaohongshu.com/explore/68a2f57d000000001c03e65f",
"tags": ["亲子游", "草原"],
"tags": [
"亲子游",
"草原"
],
"ratio": "horizontal",
"summary": "带3岁宝宝去草原,之前还担心孩子太小不适合。结果呼籁安排的行程完全考虑到了小朋友的需求车上有儿童座椅,行程节奏慢,中午有午休,酒店选的都是有暖气有热水的。宝宝每天都很开心,回来后天天说想去草原找小马。"
},
{
"id": "68c51cc7000000001d007912",
"title": "完美的内蒙之旅",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68c51cc7000000001d007912.webp",
"author": "萌萌你快来",
"likes": 1,
"url": "https://www.xiaohongshu.com/explore/68c51cc7000000001d007912",
"tags": ["内蒙", "旅行"],
"tags": [
"内蒙",
"旅行"
],
"ratio": "vertical",
"summary": "给这次内蒙之旅打满分!呼籁从前期的行程规划到全程的服务都让人很安心。特别是师傅,不仅开车稳,还会给我们讲每个地方的故事。最感动的是最后一天下雨了,师傅主动调整了行程,带我们去了一个室内的民俗体验馆,一点都没浪费时间。"
},
{
"id": "6885e207000000001d00e82b",
"title": "呼伦贝尔第四天",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-6885e207000000001d00e82b.webp",
"author": "shireyna",
"likes": 1,
"url": "https://www.xiaohongshu.com/explore/6885e207000000001d00e82b",
"tags": ["呼伦贝尔", "Day4"],
"tags": [
"呼伦贝尔",
"Day4"
],
"ratio": "vertical",
"summary": "第四天从满洲里出发去呼伦湖,路上经过了一大片油菜花田,太惊喜了!师傅看我们兴奋就停下来让我们拍了好久。呼伦湖很壮观,虽然不如想象中蓝,但是那种广阔的感觉真的震撼。晚上回到海拉尔,结束了完美的旅程。感谢呼籁!"
},
{
"id": "686b301e0000000017035ff0",
"title": "呼伦贝尔之~旅拍",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-686b301e0000000017035ff0.webp",
"author": "三人四季行",
"likes": 2,
"url": "https://www.xiaohongshu.com/explore/686b301e0000000017035ff0",
"tags": ["旅拍", "草原"],
"tags": [
"旅拍",
"草原"
],
"ratio": "horizontal",
"summary": "这次在呼伦贝尔拍了一组全家福,效果超出预期摄影师是呼籁推荐的,很有经验,知道哪个角度光线最好。拍了草原、白桦林、蒙古包三个场景,每个场景都换了服装。最终成片100多张,张张精修,性价比超高。"
},
{
"id": "68a6ee26000000001d0387b0",
"title": "还没离开呼伦贝尔,就开始有点想念了~",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68a6ee26000000001d0387b0.webp",
"author": "黎霜霜",
"likes": 1,
"url": "https://www.xiaohongshu.com/explore/68a6ee26000000001d0387b0",
"tags": ["想念", "呼伦贝尔"],
"tags": [
"想念",
"呼伦贝尔"
],
"ratio": "vertical",
"summary": "坐在回程的飞机上写这篇笔记,真的好舍不得。这几天在呼伦贝尔过的太快乐了,每天醒来都是蓝天白云绿草地,呼吸的都是新鲜空气。呼籁的师傅成了我们的朋友,约好了明年冬天再来一次看雪。呼伦贝尔,我一定会回来的!"
},
{
"id": "68d5a3f1000000001e024b90",
"title": "呼伦贝尔亲子游Day3额尔古纳湿地美翻了",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68d5a3f1000000001e024b90.webp",
"author": "小鱼妈妈育儿记",
"likes": 35,
"url": "https://www.xiaohongshu.com/explore/68d5a3f1000000001e024b90",
"tags": ["亲子游", "额尔古纳"],
"tags": [
"亲子游",
"额尔古纳"
],
"ratio": "vertical",
"summary": "今天的重头戏是额尔古纳湿地!登上山顶俯瞰整个湿地,根河像一条银色的丝带蜿蜒而过,美到窒息。呼籁的师傅带我们走了一条人少的栈道,避开了旅行团的人群。孩子在木栈道上蹦蹦跳跳,还发现了好几种小鸟。这种深度体验是跟团游完全体会不到的。"
},
{
"id": "68f2b7e50000000023018c41",
"title": "带父母去呼伦贝尔|老人家也能轻松玩",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-68f2b7e50000000023018c41.webp",
"author": "阿橙的旅行日记",
"likes": 18,
"url": "https://www.xiaohongshu.com/explore/68f2b7e50000000023018c41",
"tags": ["孝亲游", "呼伦贝尔"],
"tags": [
"孝亲游",
"呼伦贝尔"
],
"ratio": "vertical",
"summary": "这次带爸妈来呼伦贝尔,选呼籁就是看中他们的一家一车模式。老人家腿脚不方便,跟团根本不现实。呼籁的师傅全程照顾,上下车都会搀扶,景点走累了就在车上休息。行程也帮我们调整成了轻松版,每天不超过4小时车程。爸妈玩得特别开心,说这是他们最舒服的一次旅行。"
},
{
"id": "690a4c220000000010034d78",
"title": "黑山头骑马太爽了!!!",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-690a4c220000000010034d78.webp",
"author": "想吃火锅的猫",
"likes": 67,
"url": "https://www.xiaohongshu.com/explore/690a4c220000000010034d78",
"tags": ["骑马", "黑山头"],
"tags": [
"骑马",
"黑山头"
],
"ratio": "horizontal",
"summary": "到了呼伦贝尔一定要骑马!呼籁推荐的黑山头骑马场特别正规,有专业马倌一对一带领,完全不用担心安全问题。骑着马穿越草原的感觉太爽了,风吹过来,远处是连绵的山丘,真的有种策马奔腾的畅快感。而且不是那种牵着绳子走的旅游马,是真正的草原骑行!"
},
{
"id": "691bc83a0000000017023ef5",
"title": "闺蜜草原之旅|我们四个人承包了一片草原",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-691bc83a0000000017023ef5.webp",
"author": "糖果味的风",
"likes": 31,
"url": "https://www.xiaohongshu.com/explore/691bc83a0000000017023ef5",
"tags": ["闺蜜游", "草原"],
"tags": [
"闺蜜游",
"草原"
],
"ratio": "vertical",
"summary": "四个闺蜜的呼伦贝尔之旅!选了呼籁的私人定制,师傅太了解女孩子了,每到一个好看的地方就主动停车让我们拍照。还帮我们找到了一个特别美的花海,在那里拍了一下午。晚上在蒙古包附近放烟花,太浪漫了。这趟旅行的照片够我发一年朋友圈了。"
},
{
"id": "692d3f5b000000001303ab92",
"title": "呼伦贝尔的星空太震撼了",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-692d3f5b000000001303ab92.webp",
"author": "追光的人",
"likes": 45,
"url": "https://www.xiaohongshu.com/explore/692d3f5b000000001303ab92",
"tags": ["星空", "草原"],
"tags": [
"星空",
"草原"
],
"ratio": "vertical",
"summary": "在城市里从没见过这样的星空!住在呼籁安排的黑山头营地,晚上走出蒙古包,抬头就是满天繁星,银河清晰可见。师傅还教我们用手机拍星空,调参数、找角度,拍出来的效果比想象的好太多了。这种体验,花多少钱都值。"
},
{
"id": "693e8a7c000000002101c6d3",
"title": "满洲里的夜景绝了!!",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-693e8a7c000000002101c6d3.webp",
"author": "甜甜圈爱旅行",
"likes": 19,
"url": "https://www.xiaohongshu.com/explore/693e8a7c000000002101c6d3",
"tags": ["满洲里", "夜景"],
"tags": [
"满洲里",
"夜景"
],
"ratio": "horizontal",
"summary": "满洲里的夜景真的绝了!到处是俄罗斯风情的建筑,灯光一亮简直像到了异国。呼籁的师傅带我们去了一个当地人才知道的观景台,整个城市尽收眼底。还去了套娃广场,虽然有点商业化,但拍照还是很出片的。晚上在中苏街溜达,吃了正宗的俄式西餐,体验感拉满!"
},
{
"id": "694fb1990000000013027891",
"title": "呼伦贝尔7月草原绿得像画一样",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-694fb1990000000013027891.webp",
"author": "小太阳爱生活",
"likes": 56,
"url": "https://www.xiaohongshu.com/explore/694fb1990000000013027891",
"tags": ["草原", "风景"],
"tags": [
"草原",
"风景"
],
"ratio": "vertical",
"summary": "7月的呼伦贝尔草原绿得不真实,像是Windows桌面壁纸呼籁的师傅说这是一年中草原最美的时候,野花遍地,牛羊成群。我们在一个没有游客的山坡上野餐,师傅还帮我们铺了野餐垫,拿出准备好的奶茶和奶片。这种贴心的小细节太加分了。"
},
{
"id": "6960c2aa0000000021023f56",
"title": "冬天的呼伦贝尔也太美了吧",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-6960c2aa0000000021023f56.webp",
"author": "雪地里的小狐狸",
"likes": 82,
"url": "https://www.xiaohongshu.com/explore/6960c2aa0000000021023f56",
"tags": ["冬季", "雪景"],
"tags": [
"冬季",
"雪景"
],
"ratio": "vertical",
"summary": "第一次冬天来呼伦贝尔,零下30度但值了呼籁的冰雪线路真的太赞了坐雪橇、看雾凇、泼水成冰、雪地摩托……每一个都是人生初体验。师傅的车暖气开得足足的,一上车就暖和。呼籁还给我们准备了暖宝宝和防滑鞋套,细节到位。"
},
{
"id": "69723f88000000001703a4c7",
"title": "带2岁宝宝去草原的注意事项",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-69723f88000000001703a4c7.webp",
"author": "元气小妈咪",
"likes": 73,
"url": "https://www.xiaohongshu.com/explore/69723f88000000001703a4c7",
"tags": ["亲子游", "攻略"],
"tags": [
"亲子游",
"攻略"
],
"ratio": "horizontal",
"summary": "带2岁宝宝去呼伦贝尔完全可行关键是选对旅行方式。我们选了呼籁的亲子定制,车上有宝宝安全座椅,行程每天不超过3小时车程,午睡时间都留出来了。呼籁还帮我们准备了便携马桶和湿巾。宝宝在草原上追小羊的画面,治愈了我整个夏天。"
},
{
"id": "6983a4f5000000002302c8e0",
"title": "莫日格勒河的日落太绝了",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-6983a4f5000000002302c8e0.webp",
"author": "光影猎人阿杰",
"likes": 39,
"url": "https://www.xiaohongshu.com/explore/6983a4f5000000002302c8e0",
"tags": ["莫日格勒河", "日落"],
"tags": [
"莫日格勒河",
"日落"
],
"ratio": "vertical",
"summary": "莫日格勒河的日落,是我这辈子见过最美的日落之一。呼籁的师傅特意带我们到了一个私藏的观景点,避开了所有旅行团。金色的阳光洒在弯曲的河面上,牛羊在河边喝水,背景是无边的草原……按下快门的那一刻我就知道,这张照片会成为我的年度最佳。"
},
{
"id": "6994b6120000000010023d9c",
"title": "呼籁的师傅真的太好了!",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-6994b6120000000010023d9c.webp",
"author": "佩佩在路上",
"likes": 15,
"url": "https://www.xiaohongshu.com/explore/6994b6120000000010023d9c",
"tags": ["服务", "呼伦贝尔"],
"tags": [
"服务",
"呼伦贝尔"
],
"ratio": "horizontal",
"summary": "必须要夸一下呼籁的师傅!全程像家人一样照顾我们,车上常备矿泉水、零食和防蚊液。有一天我把手机忘在了景区,师傅二话不说开车帮我回去找,太感动了。最后一天还特意买了当地的特产送给我们当纪念。这种真诚的服务,下次来呼伦贝尔一定还选呼籁。"
},
{
"id": "69a5c7350000000021028a15",
"title": "白桦林里的秋色呼伦贝尔9月限定",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-69a5c7350000000021028a15.webp",
"author": "秋天的童话",
"likes": 91,
"url": "https://www.xiaohongshu.com/explore/69a5c7350000000021028a15",
"tags": ["秋季", "白桦林"],
"tags": [
"秋季",
"白桦林"
],
"ratio": "vertical",
"summary": "9月的呼伦贝尔白桦林美得不像话金黄的树叶在阳光下闪闪发光,走在林间小路上像走进了油画。呼籁的师傅带我们去了一片游客很少的白桦林,安静得只听到树叶沙沙响。秋天来呼伦贝尔的人不多,但风景一点不输夏天,强烈推荐9月来"
},
{
"id": "69b6d8480000000013038b62",
"title": "呼伦贝尔五天四晚花费明细",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-69b6d8480000000013038b62.webp",
"author": "理财小达人",
"likes": 126,
"url": "https://www.xiaohongshu.com/explore/69b6d8480000000013038b62",
"tags": ["费用", "攻略"],
"tags": [
"费用",
"攻略"
],
"ratio": "horizontal",
"summary": "很多人问呼伦贝尔旅行到底花多少钱,来一份详细的费用明细选的是呼籁5天4晚的亲子线路,包含用车、住宿、部分餐饮和体验项目。价格在同类产品中属于中等偏上,但服务品质确实对得起价格。全程没有任何隐形消费,不推销、不购物,花的每一分钱都值。"
},
{
"id": "69c7e95b000000001e02b4d7",
"title": "额尔古纳的俄罗斯族老奶奶教我做列巴",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/xhs-69c7e95b000000001e02b4d7.webp",
"author": "面包爱好者",
"likes": 24,
"url": "https://www.xiaohongshu.com/explore/69c7e95b000000001e02b4d7",
"tags": ["文化体验", "额尔古纳"],
"tags": [
"文化体验",
"额尔古纳"
],
"ratio": "vertical",
"summary": "在额尔古纳恩和镇,呼籁带我们去了一户俄罗斯族老奶奶家,学做传统列巴。老奶奶特别热情,手把手教我们揉面、发酵、烤制,还请我们喝了自制的蓝莓酱配列巴。孩子们玩得特别开心,这种深度文化体验比看景点有意义多了。呼籁的行程设计真的很走心。"
}

查看文件

@ -5,88 +5,121 @@
{
"id": "meipian-001",
"title": "呼伦贝尔|带着三个娃的草原奇妙之旅",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-meipian-001.webp",
"author": "郑佩佩",
"platform": "美篇",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["亲子游", "呼伦贝尔", "草原", "家庭旅行"],
"tags": [
"亲子游",
"呼伦贝尔",
"草原",
"家庭旅行"
],
"ratio": "horizontal",
"summary": "一家人带着三个孩子踏上呼伦贝尔之旅,选择了呼籁旅行的定制行程。从海拉尔出发,先参观了呼伦贝尔博物馆看猛犸象化石,孩子们大开眼界。草原上穿蒙古袍拍了专业旅拍,骑马穿越大草原,孩子们兴奋得不得了。在恩和镇的俄罗斯族面包房学做列巴,和驯鹿亲密接触。还体验了草原极速滑草、ATV越野,满洲里的俄罗斯风情街夜景美到窒息。全程师傅服务细致周到,行程紧凑又不疲惫,是一家人最难忘的旅行。"
},
{
"id": "meipian-002",
"title": "退休夫妇的呼伦贝尔圆梦之旅",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-meipian-002.webp",
"author": "王阿姨的慢生活",
"platform": "美篇",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["银发游", "呼伦贝尔", "慢旅行"],
"tags": [
"银发游",
"呼伦贝尔",
"慢旅行"
],
"ratio": "vertical",
"summary": "退休后一直想去看看大草原,在女儿推荐下选了呼籁旅行。师傅知道我们年纪大了,全程慢节奏,每个景点都等我们慢慢看慢慢拍。额尔古纳湿地的落日太美了,老伴拍了一百多张照片。住的酒店干净暖和,吃的都是地道蒙餐,手把肉和奶茶特别正宗。这辈子终于圆了草原梦,感谢呼籁让我们的旅途如此舒心。"
},
{
"id": "wechat-001",
"title": "呼伦贝尔6天5晚亲子游全纪录",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-wechat-001.webp",
"author": "小鱼妈妈育儿志",
"platform": "微信公众号",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["亲子游", "攻略", "呼伦贝尔"],
"tags": [
"亲子游",
"攻略",
"呼伦贝尔"
],
"ratio": "vertical",
"summary": "写了一万字的呼伦贝尔亲子游记6天5晚走了海拉尔-莫日格勒河-额尔古纳-黑山头-满洲里的经典环线。选呼籁是因为闺蜜推荐,一家一车真的太适合带娃家庭了。白桦林里给孩子做了自然教育,黑山头的骑马体验让5岁的儿子至今念念不忘。全程住宿都经过精选,卫生条件完全放心。最惊喜的是师傅自带无人机帮我们航拍了全家福,太专业了"
},
{
"id": "douyin-001",
"title": "呼伦贝尔vlog草原上的自由时光",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/3b897562f2b122626c1025cc4c36e39f/notes_pre_post/1040g3k831l0i6urf30e04149ubdcdp575b4754g!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-douyin-001.webp",
"author": "旅行的小确幸",
"platform": "抖音",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["草原", "vlog", "自驾"],
"tags": [
"草原",
"vlog",
"自驾"
],
"ratio": "horizontal",
"summary": "用镜头记录了在呼伦贝尔7天的自由时光。从第一天看到莫日格勒河的震撼,到最后一天在海拉尔依依不舍地告别师傅,每一天都值得记录。草原上骑马奔驰的画面、星空下的篝火晚会、和牧民一起赶羊群……这些画面组成了我今年最珍贵的vlog。呼籁的安排让整个旅程毫无压力,师傅还帮我找了很多好看的取景点,简直是兼职摄影助理。"
},
{
"id": "meipian-003",
"title": "我们的草原毕业旅行|青春不散场",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/0846bea5f9b467ddbdeac96e88f08b9a/1040g00831jog5ql9j4dg43klvmjdgvoeqshnj3o!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-meipian-003.webp",
"author": "四个女孩的旅行",
"platform": "美篇",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["毕业旅行", "闺蜜游", "草原"],
"tags": [
"毕业旅行",
"闺蜜游",
"草原"
],
"ratio": "vertical",
"summary": "大学毕业旅行选了呼伦贝尔大草原!四个女生第一次来北方,被草原的辽阔震撼到了。呼籁给我们安排了一位超会拍照的女司机,沿途帮我们拍了无数美照。在黑山头的日落骑马、恩和的木屋民宿、满洲里的异国风情……每一站都是惊喜。最感动的是最后一天师傅送我们去机场,车上放着我们一路旅行的照片合集视频,四个人都哭了。青春最好的模样,就是和好朋友一起看世界。"
},
{
"id": "weibo-001",
"title": "冬日呼伦贝尔零下30度的浪漫",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/caf0c832b96a87d8e21d750b8cba4cbf/notes_pre_post/1040g3k031l21j3k034204a311v5kgdpbe3rn88o!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-weibo-001.webp",
"author": "北方有佳人Lynn",
"platform": "微博",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["冬季", "雪景", "冰雪体验"],
"tags": [
"冬季",
"雪景",
"冰雪体验"
],
"ratio": "vertical",
"summary": "零下30度的呼伦贝尔,是另一种极致的美。这次走的是呼籁的冬季冰雪线路坐马拉雪橇穿越雪原、在不冻河边看雾凇、泼水成冰的瞬间让人尖叫、雪地里打滚撒欢……师傅的越野车暖气开到最足,一上车就暖和起来。呼籁还贴心准备了暖宝宝、防滑鞋套和保温杯。在冷极村的那晚,屋外零下40度,屋内暖炕热乎乎,窗外是漫天飞雪。冬天的呼伦贝尔,值得你来一次。"
},
{
"id": "wechat-002",
"title": "呼伦贝尔秋色|九月的白桦林和金色草原",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/23f201eadfa72a9fa142bc44a8cb918c/notes_pre_post/1040g3k831iugn1ccha6g4bsfm2j5qb8aelkl7h0!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-wechat-002.webp",
"author": "摄影师老张",
"platform": "微信公众号",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["秋季", "白桦林", "摄影"],
"tags": [
"秋季",
"白桦林",
"摄影"
],
"ratio": "horizontal",
"summary": "作为一名风光摄影师,我去过很多地方拍秋色,但呼伦贝尔的秋天是最让我惊艳的。九月中旬来的,白桦林金黄一片,草原渐渐变成金色,牛羊在夕阳下悠闲吃草。呼籁的师傅对本地非常熟悉,带我去了好几个普通游客根本找不到的拍摄点。日出时的额尔古纳湿地、逆光下的白桦林、满归的落叶松林……每一帧都是壁纸级别。强烈推荐9月来呼伦贝尔,人少景美,是摄影人的天堂。"
},
{
"id": "meipian-004",
"title": "带着90岁奶奶去看草原",
"cover": "https://sns-webpic-qc.xhscdn.com/202603231146/5fc9cc68d2c4db833a5ac5dfc8ed3f8e/notes_pre_post/1040g3k031k0gh6jd36004806b24ch4bbbhq2390!nc_n_webp_mw_1",
"cover": "/uploads/covers/youji-meipian-004.webp",
"author": "孙女小敏",
"platform": "美篇",
"url": "https://www.meipian.cn/5h3tmj2q",
"tags": ["孝亲游", "感动", "草原"],
"tags": [
"孝亲游",
"感动",
"草原"
],
"ratio": "vertical",
"summary": "奶奶今年90岁了,一辈子生活在南方小城,最大的心愿就是看看北方的大草原。今年终于鼓起勇气带她出发了。选呼籁是因为他们可以完全定制行程,根据奶奶的身体状况调整节奏。师傅特别耐心,每次停车都先下来帮奶奶开门搀扶。在草原上,奶奶激动得眼眶湿润,说这辈子值了。师傅还特意找了一个平坦的草地,铺好垫子让奶奶坐着看远方。这是我做过最正确的决定,感谢呼籁让奶奶圆了草原梦。"
}

查看文件

@ -15,7 +15,7 @@ export default defineNuxtConfig({
'/products/v9-6d5n-family', '/products/v9-6d5n-explore',
'/products/v9-7d6n-family', '/products/v9-7d6n-wild',
'/products/autumn', '/products/winter',
'/gallery', '/news', '/calendar', '/stories',
'/gallery', '/news', '/calendar', '/stories', '/xiaohongshu', '/youji',
'/blog',
'/blog/hulunbuir-travel-guide-2026',
'/blog/hulunbuir-family-travel-guide',

查看文件

@ -126,6 +126,17 @@ function addNote() {
async function save() {
saving.value = true
try {
//
for (const note of data.value.notes) {
if (note.cover && note.cover.startsWith('http')) {
try {
const res = await adminFetch('/api/admin/download-image', { method: 'POST', body: { url: note.cover } })
note.cover = res.localUrl
} catch {
// URL
}
}
}
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'xiaohongshu-wall', data: data.value } })
message.success('已保存')
} catch {

查看文件

@ -113,6 +113,17 @@ function addNote() {
async function save() {
saving.value = true
try {
//
for (const note of data.value.notes) {
if (note.cover && note.cover.startsWith('http')) {
try {
const res = await adminFetch('/api/admin/download-image', { method: 'POST', body: { url: note.cover } })
note.cover = res.localUrl
} catch {
// URL
}
}
}
await adminFetch('/api/admin/site-content', { method: 'PUT', body: { key: 'youji', data: data.value } })
message.success('已保存')
} catch {

查看文件

@ -46,7 +46,7 @@
</p>
<div class="success-wechat">
<span class="wechat-label">也可主动添加定制师微信</span>
<span class="wechat-id">{{ customize.contact.wechat }}</span>
<img src="/images/contact/qrcode-wechat.jpg" alt="定制师微信二维码" class="wechat-qrcode wechat-qrcode--success" />
</div>
<p class="success-tip">{{ customize.contact.tip }}</p>
<button class="btn-reset" @click="resetForm">重新填写</button>
@ -249,8 +249,8 @@
<div class="aside-card aside-wechat">
<h3 class="aside-title">不想填表</h3>
<p class="aside-desc">直接添加定制师微信随时咨询</p>
<div class="wechat-id-display">{{ customize.contact.wechat }}</div>
<p class="aside-desc">扫码添加定制师微信随时咨询</p>
<img src="/images/contact/qrcode-wechat.jpg" alt="定制师微信二维码" class="wechat-qrcode" />
<p class="wechat-tip">{{ customize.contact.tip }}</p>
</div>
</aside>
@ -974,16 +974,20 @@ select,
border-color: transparent;
}
.wechat-id-display {
font-size: @font-size-lg;
font-weight: 700;
color: @color-primary;
padding: @space-sm @space-md;
background: @color-bg-white;
.wechat-qrcode {
display: block;
width: 180px;
height: 180px;
object-fit: contain;
margin: @space-sm auto;
border-radius: @border-radius-md;
text-align: center;
margin-bottom: @space-sm;
letter-spacing: 1px;
border: 1px solid @color-border;
background: @color-bg-white;
&--success {
width: 160px;
height: 160px;
}
}
.wechat-tip {

查看文件

@ -14,7 +14,7 @@
<main class="detail-main">
<!-- 封面图 -->
<div class="detail-cover" :class="{ 'detail-cover--wide': note.ratio === 'horizontal' }">
<img :src="note.cover" :alt="note.title" />
<img :src="proxyImage(note.cover)" :alt="note.title" referrerpolicy="no-referrer" />
</div>
<!-- 笔记正文预览 -->
@ -126,7 +126,7 @@
:class="{ 'sidebar-note--active': n.id === note.id }"
>
<NuxtLink :to="`/xiaohongshu/${n.id}`" class="sidebar-note__link">
<img :src="n.cover" :alt="n.title" class="sidebar-note__thumb" />
<img :src="proxyImage(n.cover)" :alt="n.title" class="sidebar-note__thumb" referrerpolicy="no-referrer" />
<div class="sidebar-note__info">
<span class="sidebar-note__title">{{ n.title }}</span>
<span class="sidebar-note__author">@{{ n.author }}</span>
@ -163,6 +163,7 @@
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
import { proxyImage } from '~/composables/useImageProxy'
const { data: wallData } = await usePublicApi('xiaohongshu-wall')

查看文件

@ -102,9 +102,10 @@
>
<div class="note-card__cover" :class="{ 'note-card__cover--wide': note.ratio === 'horizontal' }">
<img
:src="note.cover"
:src="proxyImage(note.cover)"
:alt="note.title"
loading="lazy"
referrerpolicy="no-referrer"
@error="handleImageError"
/>
<div class="note-card__overlay">
@ -160,6 +161,7 @@
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
import { proxyImage } from '~/composables/useImageProxy'
const { data: wallData } = await usePublicApi('xiaohongshu-wall')

查看文件

@ -13,7 +13,7 @@
<main class="detail-main">
<!-- 封面 -->
<div class="detail-cover" :class="{ 'detail-cover--wide': note.ratio === 'horizontal' }">
<img :src="note.cover" :alt="note.title" />
<img :src="proxyImage(note.cover)" :alt="note.title" referrerpolicy="no-referrer" />
</div>
<!-- 正文 -->
@ -97,7 +97,7 @@
:class="{ 'sidebar-note--active': n.id === note.id }"
>
<NuxtLink :to="`/youji/${n.id}`" class="sidebar-note__link">
<img :src="n.cover" :alt="n.title" class="sidebar-note__thumb" />
<img :src="proxyImage(n.cover)" :alt="n.title" class="sidebar-note__thumb" referrerpolicy="no-referrer" />
<div class="sidebar-note__info">
<span class="sidebar-note__title">{{ n.title }}</span>
<span class="sidebar-note__meta">
@ -143,6 +143,7 @@
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
import { proxyImage } from '~/composables/useImageProxy'
const { data: youjiData } = await usePublicApi('youji')

查看文件

@ -93,7 +93,7 @@
class="note-card"
>
<div class="note-card__cover" :class="{ 'note-card__cover--wide': note.ratio === 'horizontal' }">
<img :src="note.cover" :alt="note.title" loading="lazy" @error="handleImageError" />
<img :src="proxyImage(note.cover)" :alt="note.title" loading="lazy" referrerpolicy="no-referrer" @error="handleImageError" />
<div class="note-card__overlay">
<span class="note-card__read">查看详情</span>
</div>
@ -136,6 +136,7 @@
<script setup>
import { useBreadcrumbSchema } from '~/composables/useJsonLd'
import { proxyImage } from '~/composables/useImageProxy'
const { data: youjiData } = await usePublicApi('youji')

132
scripts/download-covers.mjs 普通文件
查看文件

@ -0,0 +1,132 @@
/**
* 下载小红书/游记封面图片到本地
*
* 小红书 CDN 签名 URL 会过期且有防盗链
* 此脚本将封面图下载到 public/uploads/covers/ 并更新数据库
*
* 运行: node scripts/download-covers.mjs
*/
import Database from 'better-sqlite3'
import { writeFileSync, mkdirSync, existsSync } from 'fs'
import { join, dirname } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const ROOT = join(__dirname, '..')
const DB_PATH = join(ROOT, 'server/database/hulai.db')
const COVERS_DIR = join(ROOT, 'public/uploads/covers')
// 确保目录存在
mkdirSync(COVERS_DIR, { recursive: true })
/**
* 将签名 CDN URL 转为永久可下载的 URL
*/
function toPermanentUrl(url) {
try {
const parsed = new URL(url)
if (parsed.hostname === 'sns-webpic-qc.xhscdn.com') {
const parts = parsed.pathname.split('/')
if (parts.length >= 4) {
const imagePath = parts.slice(3).join('/')
return `https://ci.xiaohongshu.com/${imagePath}`
}
}
// 已经是永久链接
if (parsed.hostname === 'ci.xiaohongshu.com') {
return url
}
} catch {}
return url
}
/**
* 下载图片到本地
*/
async function downloadImage(url, filename) {
const localPath = join(COVERS_DIR, filename)
if (existsSync(localPath)) {
console.log(` ⏭ 已存在: ${filename}`)
return `/uploads/covers/${filename}`
}
const downloadUrl = toPermanentUrl(url)
try {
const res = await fetch(downloadUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
},
})
if (!res.ok) {
console.log(` ✗ 下载失败 (${res.status}): ${filename} <- ${downloadUrl}`)
return null
}
const buffer = Buffer.from(await res.arrayBuffer())
writeFileSync(localPath, buffer)
console.log(` ✓ 已下载: ${filename} (${(buffer.length / 1024).toFixed(1)}KB)`)
return `/uploads/covers/${filename}`
} catch (err) {
console.log(` ✗ 下载出错: ${filename} - ${err.message}`)
return null
}
}
/**
* URL 中提取文件扩展名
*/
function getExtFromUrl(url) {
if (url.includes('webp')) return '.webp'
if (url.includes('.jpg') || url.includes('.jpeg')) return '.jpg'
if (url.includes('.png')) return '.png'
return '.webp' // 小红书默认 webp
}
async function main() {
const db = new Database(DB_PATH)
for (const key of ['xiaohongshu-wall', 'youji']) {
console.log(`\n📦 处理: ${key}`)
const row = db.prepare('SELECT data FROM site_content WHERE key = ?').get(key)
if (!row) {
console.log(` ⚠ 未找到数据: ${key}`)
continue
}
const data = JSON.parse(row.data)
const notes = data.notes || []
let updated = 0
for (const note of notes) {
if (!note.cover) continue
// 跳过已经是本地路径的
if (note.cover.startsWith('/')) {
console.log(` ⏭ 已是本地: ${note.id}`)
continue
}
const ext = getExtFromUrl(note.cover)
const filename = `${key.replace('xiaohongshu-wall', 'xhs')}-${note.id}${ext}`
const localUrl = await downloadImage(note.cover, filename)
if (localUrl) {
note.cover = localUrl
updated++
}
}
if (updated > 0) {
db.prepare('UPDATE site_content SET data = ? WHERE key = ?')
.run(JSON.stringify(data), key)
console.log(`\n ✅ 已更新 ${updated} 张封面到本地路径`)
} else {
console.log(`\n 无需更新`)
}
}
db.close()
console.log('\n🎉 完成!')
}
main().catch(console.error)

查看文件

@ -0,0 +1,91 @@
import { writeFileSync, existsSync, mkdirSync } from 'fs'
import { join } from 'path'
import { requireAdmin } from '../../utils/auth.js'
/**
* 下载外部图片到本地
* POST /api/admin/download-image
* Body: { url: "https://..." }
* Returns: { localUrl: "/uploads/covers/xxx.webp" }
*/
const ALLOWED_HOSTS = [
'sns-webpic-qc.xhscdn.com',
'ci.xiaohongshu.com',
'sns-img-bd.xhscdn.com',
]
/**
* 将签名 CDN URL 转为永久可下载的 URL
*/
function toPermanentUrl(url) {
try {
const parsed = new URL(url)
if (parsed.hostname === 'sns-webpic-qc.xhscdn.com') {
const parts = parsed.pathname.split('/')
if (parts.length >= 4) {
const imagePath = parts.slice(3).join('/')
return `https://ci.xiaohongshu.com/${imagePath}`
}
}
} catch {}
return url
}
export default defineEventHandler(async (event) => {
requireAdmin(event)
const { url } = await readBody(event)
if (!url || !url.startsWith('http')) {
throw createError({ statusCode: 400, message: '请提供有效的图片 URL' })
}
// 验证域名白名单
let parsedUrl
try {
parsedUrl = new URL(url)
} catch {
throw createError({ statusCode: 400, message: '无效的 URL' })
}
if (!ALLOWED_HOSTS.includes(parsedUrl.hostname)) {
throw createError({ statusCode: 403, message: '该域名不支持自动下载,请手动上传图片' })
}
// 生成本地文件名
const pathParts = parsedUrl.pathname.split('/')
const imageId = pathParts[pathParts.length - 1].split('!')[0] || `img-${Date.now()}`
const filename = `${imageId}.webp`
const coversDir = join(process.cwd(), 'public', 'uploads', 'covers')
if (!existsSync(coversDir)) {
mkdirSync(coversDir, { recursive: true })
}
const localPath = join(coversDir, filename)
const localUrl = `/uploads/covers/${filename}`
// 如果已下载过,直接返回
if (existsSync(localPath)) {
return { localUrl }
}
// 下载图片
const downloadUrl = toPermanentUrl(url)
try {
const res = await fetch(downloadUrl, {
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
},
})
if (!res.ok) {
throw createError({ statusCode: 502, message: `图片下载失败 (${res.status})` })
}
const buffer = Buffer.from(await res.arrayBuffer())
writeFileSync(localPath, buffer)
return { localUrl }
} catch (err) {
if (err.statusCode) throw err
throw createError({ statusCode: 502, message: `图片下载出错: ${err.message}` })
}
})

查看文件

@ -0,0 +1,59 @@
/**
* 图片代理接口
* 用于加载有防盗链保护的外部图片如小红书 CDN
*
* 用法: /api/image-proxy?url=https://sns-webpic-qc.xhscdn.com/...
*
* 安全限制只允许代理白名单域名的图片
*/
export default defineEventHandler(async (event) => {
const { url } = getQuery(event)
if (!url) {
throw createError({ statusCode: 400, message: '缺少 url 参数' })
}
// 白名单:只允许代理指定域名的图片
const allowedHosts = [
'sns-webpic-qc.xhscdn.com',
'ci.xiaohongshu.com',
'image.xiaohongshu.com',
'picx.zhimg.com',
]
let parsedUrl
try {
parsedUrl = new URL(url)
} catch {
throw createError({ statusCode: 400, message: '无效的 URL' })
}
if (!allowedHosts.includes(parsedUrl.hostname)) {
throw createError({ statusCode: 403, message: '该域名不在白名单中' })
}
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'image/webp,image/apng,image/*,*/*;q=0.8',
},
})
if (!response.ok) {
throw createError({ statusCode: response.status, message: '图片获取失败' })
}
const contentType = response.headers.get('content-type') || 'image/jpeg'
const buffer = await response.arrayBuffer()
setHeader(event, 'Content-Type', contentType)
setHeader(event, 'Cache-Control', 'public, max-age=86400, s-maxage=604800')
setHeader(event, 'Access-Control-Allow-Origin', '*')
return Buffer.from(buffer)
} catch (err) {
if (err.statusCode) throw err
throw createError({ statusCode: 502, message: '图片代理请求失败' })
}
})