perf(mp-weixin): 照片分两层,详情页移入 pages-poi 分包

微信主包上限 2 MB,30 张 800×600 照片单独就 2.6 MB,
整包 3.2 MB,上传被拒(错误码 80051)。

按使用场景把照片拆成两层:
- 主包缩略图 400×300 q75(678 KB),供地图、行程、打卡卡片使用,
  这些场景最大只显示 192rpx(≈288 px @DPR3),800×600 是过剩
- 分包大图 800×600 q66(1832 KB),供详情页 hero 使用,
  随 pages-poi 分包按需下载

两层都保持 4:3,PoiImage 的 aspectFill 行为不变,视觉表现与之前一致。

详情页从 pages/poi/detail 移到 pages-poi/detail:主包页面无法引用
分包资源,hero 要用大图,页面就必须在分包内。新增
coverPhotoFullUrlFor() 供分包内页面取大图,占位图仍指向主包。

verify-weixin-output.mjs 增加防回归检查:分包注册、两层照片数量
一致、主包与各分包实际体积(留 64 KB 余量)。包体再超限会在
verify 阶段失败,不必等上传才发现。

process-poi-photos.mjs 重写为双层产出,幂等,末尾报告包体占用并
在超限时退出非零。原图改从 src/photo-intake/ 读取(不提交仓库)。

改造后:主包 1522 KB、分包 pages-poi 1847 KB,均在 2048 KB 内。
mp-weixin 与 H5 均编译通过,97 个测试通过,vue-tsc 与 ESLint 无错。
This commit is contained in:
周瑞哲
2026-08-03 09:21:23 +08:00
parent 9e8c7bf908
commit f8ad5e1faf
72 changed files with 327 additions and 108 deletions
+25 -6
View File
@@ -1,11 +1,14 @@
/**
* POI 封面照片注册表
*
* 照片分两层存放,因为微信小程序主包上限 2 MB,30 张大图单独就超限:
* - 缩略图 400×300 放主包,供地图、行程、打卡等卡片使用(最大 192rpx ≈ 288 px
* - 大图 800×600 放 pages-poi 分包,供详情页 hero 使用,随分包按需下载
*
* 如何添加一张真实照片:
* 1. 压缩图片:800×600 px4:3 横版),JPEG 质量 75-82,文件 ≤ 120 KB
* 2. 命名为 <poiId>.jpg(例如 poi_gm_culture_center.jpg
* 3. 放入 src/static/poi/photos/ 目录
* 4. 将 poiId 加入下方 POI_COVER_PHOTOS_READY 集合
* 1. 原图放入 src/photo-intake/(不提交仓库),命名为 <poiId>.jpg
* 2. 跑 `node scripts/process-poi-photos.mjs` 生成两层图片
* 3. 将 poiId 加入下方 POI_COVER_PHOTOS_READY 集合
*
* 未在此集合中的 POI 会继续使用占位图 /static/poi/placeholder.png
*/
@@ -49,10 +52,26 @@ export const POI_COVER_PHOTOS_READY: ReadonlySet<string> = new Set<string>([
'poi_amap_b0jb2aurvw', // 五指耙森林公园(光明片区)
])
const POI_PHOTOS_BASE_URL = '/static/poi/photos'
/** 主包缩略图目录(400×300 */
const POI_THUMB_BASE_URL = '/static/poi/photos'
/** pages-poi 分包大图目录(800×600),仅分包内页面可引用 */
const POI_FULL_BASE_URL = '/pages-poi/static/photos'
/**
* 卡片用缩略图 URL。主包内任何页面都可用。
*/
export function coverPhotoUrlFor(poiId: string): string {
return `${POI_PHOTOS_BASE_URL}/${poiId}.jpg`
return `${POI_THUMB_BASE_URL}/${poiId}.jpg`
}
/**
* 详情页 hero 用大图 URL。
*
* 只有 pages-poi 分包内的页面能引用 —— 分包资源要等分包下载后才存在,
* 主包页面拿到这个路径会加载失败。
*/
export function coverPhotoFullUrlFor(poiId: string): string {
return `${POI_FULL_BASE_URL}/${poiId}.jpg`
}
export function hasCoverPhoto(poiId: string): boolean {
@@ -1,9 +1,10 @@
<script setup lang="ts">
import type { PoiResolved } from '@/domain/poi'
import type { PoiImageAsset, PoiResolved } from '@/domain/poi'
import PageState from '@/components/poi/PageState.vue'
import PoiHeroGallery from '@/components/poi/PoiHeroGallery.vue'
import PoiTagList from '@/components/poi/PoiTagList.vue'
import { getPoiRepository } from '@/data/poi'
import { coverPhotoFullUrlFor } from '@/data/poi/photos'
import { formatRecommendationLabel } from '@/domain/poi'
import { getCheckInProfile } from '@/services/check-in'
@@ -12,6 +13,24 @@ const poi = ref<PoiResolved | null>(null)
const hasCheckedIn = ref(false)
const checkInStorageError = ref(false)
/**
* hero 用分包大图800×600替换主包缩略图400×300
*
* 本页在 pages-poi 分包内能引用分包 static 资源主包页面不行
* 占位图仍指向主包不做替换
*/
const heroImages = computed<PoiImageAsset[]>(() => {
const current = poi.value
if (!current)
return []
return current.images.map(image => (
image.id === current.coverImageId && !image.isPlaceholder
? { ...image, url: coverPhotoFullUrlFor(current.id) }
: image
))
})
function loadPoi(poiId: string) {
state.value = 'loading'
poi.value = null
@@ -124,7 +143,7 @@ onShow(() => {
/>
<template v-else-if="poi">
<PoiHeroGallery :images="poi.images" :name="poi.name" />
<PoiHeroGallery :images="heroImages" :name="poi.name" />
<view class="poi-detail-page__content">
<view class="poi-detail-page__header">
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

+18 -12
View File
@@ -83,17 +83,6 @@
"navigationBarTextStyle": "black",
"backgroundColor": "#f4f8f6"
}
},
{
"path": "pages/poi/detail",
"type": "page",
"layout": "map",
"style": {
"navigationBarTitleText": "点位详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f7f6"
}
}
],
"globalStyle": {
@@ -131,5 +120,22 @@
}
]
},
"subPackages": []
"subPackages": [
{
"root": "pages-poi",
"pages": [
{
"path": "detail",
"type": "page",
"layout": "map",
"style": {
"navigationBarTitleText": "点位详情",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"backgroundColor": "#f5f7f6"
}
}
]
}
]
}
+1 -1
View File
@@ -66,7 +66,7 @@ function openItinerary() {
}
function openDetail(poiId: string) {
uni.navigateTo({ url: `/pages/poi/detail?poiId=${encodeURIComponent(poiId)}` })
uni.navigateTo({ url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}` })
}
function focusOnMap(poiId: string) {
+1 -1
View File
@@ -53,7 +53,7 @@ function openPoi(poiId: string) {
})
return
}
uni.navigateTo({ url: `/pages/poi/detail?poiId=${encodeURIComponent(poiId)}` })
uni.navigateTo({ url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}` })
}
function loadProfile() {
+1 -1
View File
@@ -284,7 +284,7 @@ function openPlanner() {
}
function openDetail(poiId: string) {
uni.navigateTo({ url: `/pages/poi/detail?poiId=${encodeURIComponent(poiId)}` })
uni.navigateTo({ url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}` })
}
function focusOnMap(poiId: string) {
+1 -1
View File
@@ -475,7 +475,7 @@ function openDetail(poiId: string) {
return
navigating.value = true
uni.navigateTo({
url: `/pages/poi/detail?poiId=${encodeURIComponent(poiId)}`,
url: `/pages-poi/detail?poiId=${encodeURIComponent(poiId)}`,
complete: () => {
setTimeout(() => {
navigating.value = false
Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 29 KiB