feat(image-cache): 新增图片本地缓存并全站接入
引入 cached-image 组件与 image-cache 工具,实现签名 URL 稳定 key 剥离、downloadFile 落盘及 TTL/LRU 清理,替换 11 个页面与 clothes-card 共 22 处网络图片渲染点,减少 CDN 重复流量。
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"component": true,
|
||||
"styleIsolation": "isolated"
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// cached-image —— 通用图片组件:网络图经 utils/image-cache 落盘后展示(命中时零网络请求),
|
||||
// 本地路径(wxfile:// / http://tmp / 包内路径等)原样直出;
|
||||
// 未命中缓存时先骨架占位、下载完成后显示本地文件,失败自动回退网络地址,功能不受影响。
|
||||
|
||||
import { getCachedLocalPath, resolveImage, dropImageCache } from '../../utils/image-cache'
|
||||
|
||||
Component({
|
||||
properties: {
|
||||
src: { type: String, value: '' },
|
||||
// 与 image 组件对齐:未显式传入时保持 scaleToFill 默认行为
|
||||
mode: { type: String, value: 'scaleToFill' },
|
||||
lazyLoad: { type: Boolean, value: false },
|
||||
},
|
||||
data: {
|
||||
// 实际渲染地址:本地文件路径或回退的网络地址;空串 = 骨架占位中
|
||||
shown: '',
|
||||
},
|
||||
observers: {
|
||||
src() {
|
||||
this.refresh()
|
||||
},
|
||||
},
|
||||
lifetimes: {
|
||||
attached() {
|
||||
;(this as any)._alive = true
|
||||
this.refresh()
|
||||
},
|
||||
detached() {
|
||||
;(this as any)._alive = false
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
refresh() {
|
||||
const src = this.data.src
|
||||
const seq = (((this as any)._seq as number) || 0) + 1
|
||||
;(this as any)._seq = seq
|
||||
|
||||
if (!src) {
|
||||
this.setData({ shown: '' })
|
||||
return
|
||||
}
|
||||
if (src.indexOf('https://') !== 0) {
|
||||
// 本地/包内路径直接渲染,不进入缓存流程
|
||||
this.setData({ shown: src })
|
||||
return
|
||||
}
|
||||
const hit = getCachedLocalPath(src)
|
||||
if (hit) {
|
||||
this.setData({ shown: hit })
|
||||
return
|
||||
}
|
||||
// 未命中:先占位,下载落盘后显示本地文件(期间不渲染网络地址,避免额外请求抵消缓存收益)
|
||||
if (this.data.shown) this.setData({ shown: '' })
|
||||
resolveImage(src).then(
|
||||
(localPath) => {
|
||||
if ((this as any)._alive === false || (this as any)._seq !== seq) return
|
||||
this.setData({ shown: localPath })
|
||||
},
|
||||
() => {
|
||||
if ((this as any)._alive === false || (this as any)._seq !== seq) return
|
||||
// 缓存不可用时回退网络地址,保证功能可用
|
||||
this.setData({ shown: src })
|
||||
}
|
||||
)
|
||||
},
|
||||
onImgError() {
|
||||
const shown = this.data.shown
|
||||
const src = this.data.src
|
||||
// 网络地址自身失败或非网络图:不再处理,避免循环回退
|
||||
if (!shown || !src || shown.indexOf('https://') === 0) return
|
||||
;(this as any)._seq = (((this as any)._seq as number) || 0) + 1
|
||||
// 本地文件失效(被系统清理/损坏):清理缓存并回退网络地址一次
|
||||
dropImageCache(src)
|
||||
this.setData({ shown: src })
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,10 @@
|
||||
<!-- 通用图片:缓存命中直出本地文件,未命中先骨架占位,失败回退网络地址 -->
|
||||
<view wx:if="{{!shown}}" class="ci-ph"></view>
|
||||
<image
|
||||
wx:if="{{shown}}"
|
||||
class="ci-img"
|
||||
src="{{shown}}"
|
||||
mode="{{mode}}"
|
||||
lazy-load="{{lazyLoad}}"
|
||||
binderror="onImgError"
|
||||
/>
|
||||
@@ -0,0 +1,26 @@
|
||||
/* 宿主节点:对齐 image 组件默认 inline-block 布局,尺寸与圆角由调用方 class 控制 */
|
||||
:host {
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 内层图片:填满宿主并继承圆角,保证既有版式零回归 */
|
||||
.ci-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
/* 骨架占位:与全局 .sk 同款视觉,组件内独立定义避免依赖外部样式加载顺序 */
|
||||
.ci-ph {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, #F2F2F0 25%, #E9E9E6 37%, #F2F2F0 63%);
|
||||
background-size: 400% 100%;
|
||||
animation: ciSkLoad 1.5s ease infinite;
|
||||
}
|
||||
@keyframes ciSkLoad {
|
||||
0% { background-position: 100% 50%; }
|
||||
100% { background-position: 0 50%; }
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"component": true,
|
||||
"styleIsolation": "apply-shared"
|
||||
"styleIsolation": "apply-shared",
|
||||
"usingComponents": {
|
||||
"cached-image": "/components/cached-image/cached-image"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!-- v2 衣物瓦片:无卡片容器,图下两行(衬线品名 + 灰色属性行) -->
|
||||
<view class="tile {{selected ? 'is-picked' : ''}}" bindtap="onTap">
|
||||
<view class="tile-img">
|
||||
<image
|
||||
<cached-image
|
||||
wx:if="{{item.processed_image}}"
|
||||
class="pic"
|
||||
src="{{item.processed_image}}"
|
||||
mode="aspectFill"
|
||||
lazy-load
|
||||
lazy-load="{{true}}"
|
||||
/>
|
||||
<view wx:else class="ph icon icon-hanger"></view>
|
||||
<view wx:if="{{statusText}}" class="tile-flag {{statusType === 'ink' ? 'is-ink' : ''}}">{{statusText}}</view>
|
||||
|
||||
Reference in New Issue
Block a user