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 无错。
@@ -1,11 +1,14 @@
|
||||
/**
|
||||
* POI 封面照片注册表
|
||||
*
|
||||
* 照片分两层存放,因为微信小程序主包上限 2 MB,30 张大图单独就超限:
|
||||
* - 缩略图 400×300 放主包,供地图、行程、打卡等卡片使用(最大 192rpx ≈ 288 px)
|
||||
* - 大图 800×600 放 pages-poi 分包,供详情页 hero 使用,随分包按需下载
|
||||
*
|
||||
* 如何添加一张真实照片:
|
||||
* 1. 压缩图片:800×600 px(4: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">
|
||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 89 KiB |
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 79 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 29 KiB |