|
|
@@ -33,8 +33,8 @@
|
|
|
<text class="section-title">最近使用</text>
|
|
|
<view class="quick-grid">
|
|
|
<view v-for="app in quickApps" :key="app.id" class="app-tile" @click="openApp(app)">
|
|
|
- <view class="tile-icon" :style="{ background: app.iconBg || defaultIconBg }">
|
|
|
- <image class="tile-icon-img" :src="app.iconPath" mode="aspectFit" />
|
|
|
+ <view class="tile-icon" :style="{ background: app.useRealIcon ? '#f3f4f6' : (app.iconBg || defaultIconBg) }">
|
|
|
+ <image class="tile-icon-img" :class="{ 'default-icon': !app.useRealIcon, 'real-icon': app.useRealIcon }" :src="app.iconPath" mode="aspectFit" />
|
|
|
<view
|
|
|
v-if="app.badge"
|
|
|
class="tile-badge"
|
|
|
@@ -90,8 +90,8 @@
|
|
|
class="app-tile app-tile-4"
|
|
|
@click="openApp(app)"
|
|
|
>
|
|
|
- <view class="tile-icon" :style="{ background: app.iconBg || defaultIconBg }">
|
|
|
- <image class="tile-icon-img" :src="app.iconPath" mode="aspectFit" />
|
|
|
+ <view class="tile-icon" :style="{ background: app.useRealIcon ? '#f3f4f6' : (app.iconBg || defaultIconBg) }">
|
|
|
+ <image class="tile-icon-img" :class="{ 'default-icon': !app.useRealIcon, 'real-icon': app.useRealIcon }" :src="app.iconPath" mode="aspectFit" />
|
|
|
<view
|
|
|
v-if="app.badge"
|
|
|
class="tile-badge"
|
|
|
@@ -157,6 +157,38 @@
|
|
|
return pair ? `linear-gradient(135deg, ${pair[0]} 0%, ${pair[1]} 100%)` : ''
|
|
|
}
|
|
|
|
|
|
+ // ---- 图标本地缓存 ----
|
|
|
+ const CACHE_DIR = '_app_icons_'
|
|
|
+ const CACHE_MAP_KEY = 'app_icon_cache_map'
|
|
|
+
|
|
|
+ function getIconCacheMap() {
|
|
|
+ try {
|
|
|
+ const raw = uni.getStorageSync(CACHE_MAP_KEY)
|
|
|
+ return (raw && typeof raw === 'object') ? raw : {}
|
|
|
+ } catch (e) { return {} }
|
|
|
+ }
|
|
|
+
|
|
|
+ function setIconCacheMap(map) {
|
|
|
+ try { uni.setStorageSync(CACHE_MAP_KEY, map || {}) } catch (e) {}
|
|
|
+ }
|
|
|
+
|
|
|
+ async function downloadAndCacheIcon(iconUrl, iconObjectKey) {
|
|
|
+ try {
|
|
|
+ const fs = uni.getFileSystemManager()
|
|
|
+ try { fs.accessSync(CACHE_DIR) } catch (e) { fs.mkdirSync(CACHE_DIR) }
|
|
|
+
|
|
|
+ const localPath = `${CACHE_DIR}/${encodeURIComponent(iconObjectKey)}.png`
|
|
|
+ try { fs.unlinkSync(localPath) } catch (e) {}
|
|
|
+
|
|
|
+ const res = await uni.downloadFile({ url: iconUrl })
|
|
|
+ if (res.statusCode === 200) {
|
|
|
+ fs.copyFileSync(res.tempFilePath, localPath)
|
|
|
+ return localPath
|
|
|
+ }
|
|
|
+ } catch (e) {}
|
|
|
+ return ''
|
|
|
+ }
|
|
|
+
|
|
|
export default {
|
|
|
components: { UserAvatar },
|
|
|
data() {
|
|
|
@@ -320,12 +352,16 @@
|
|
|
|
|
|
const appList = activeItems.map((it) => {
|
|
|
const categoryId = it.category_id == null || it.category_id === '' ? 'uncat' : String(it.category_id)
|
|
|
+ const iconUrl = it.icon_url || ''
|
|
|
+ const iconObjectKey = it.icon_object_key || ''
|
|
|
return {
|
|
|
id: it.app_id,
|
|
|
name: it.app_name,
|
|
|
- // 与聊天列表 SystemAvatar 保持一致
|
|
|
- iconPath: '/static/icons/application.svg',
|
|
|
- iconBg: getGradientBgByName(it.app_name),
|
|
|
+ iconPath: iconUrl || '/static/icons/application.svg',
|
|
|
+ iconBg: iconUrl ? '#f3f4f6' : getGradientBgByName(it.app_name),
|
|
|
+ iconObjectKey,
|
|
|
+ useRealIcon: !!(iconUrl && iconObjectKey),
|
|
|
+
|
|
|
category: categoryId,
|
|
|
categoryName: it.category_name || '未分类',
|
|
|
description: it.description || '',
|
|
|
@@ -338,6 +374,58 @@
|
|
|
|
|
|
this.allApps = appList
|
|
|
|
|
|
+ // --- 图标缓存处理:用 icon_object_key 做缓存键,key 不变时不重复下载 ---
|
|
|
+ const cacheMap = getIconCacheMap()
|
|
|
+ const newCacheMap = {}
|
|
|
+ const iconDownloadTasks = []
|
|
|
+
|
|
|
+ for (const app of appList) {
|
|
|
+ if (app.useRealIcon && app.iconObjectKey) {
|
|
|
+ const key = String(app.iconObjectKey)
|
|
|
+ newCacheMap[key] = cacheMap[key] || ''
|
|
|
+
|
|
|
+ if (cacheMap[key]) {
|
|
|
+ // 校验缓存文件未被系统清理
|
|
|
+ let fileExists = false
|
|
|
+ try {
|
|
|
+ const fs = uni.getFileSystemManager()
|
|
|
+ fs.accessSync(cacheMap[key])
|
|
|
+ fileExists = true
|
|
|
+ } catch (e) {}
|
|
|
+
|
|
|
+ if (fileExists) {
|
|
|
+ app.iconPath = cacheMap[key]
|
|
|
+ } else {
|
|
|
+ iconDownloadTasks.push(
|
|
|
+ downloadAndCacheIcon(app.iconPath, key).then(localPath => {
|
|
|
+ if (localPath) {
|
|
|
+ app.iconPath = localPath
|
|
|
+ newCacheMap[key] = localPath
|
|
|
+ }
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ iconDownloadTasks.push(
|
|
|
+ downloadAndCacheIcon(app.iconPath, key).then(localPath => {
|
|
|
+ if (localPath) {
|
|
|
+ app.iconPath = localPath
|
|
|
+ newCacheMap[key] = localPath
|
|
|
+ }
|
|
|
+ })
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (iconDownloadTasks.length > 0) {
|
|
|
+ Promise.all(iconDownloadTasks).then(() => {
|
|
|
+ setIconCacheMap(newCacheMap)
|
|
|
+ this.quickApps = [...this.quickApps]
|
|
|
+ this.allApps = [...this.allApps]
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
// 从本地最近列表拼出顶部“最近使用”
|
|
|
const recentList = this.loadRecentApps()
|
|
|
.filter((x) => x && x.appId != null)
|
|
|
@@ -560,10 +648,19 @@
|
|
|
.tile-icon-img {
|
|
|
width: 52rpx;
|
|
|
height: 52rpx;
|
|
|
- /* 与聊天列表 SystemAvatar 一致:确保图标居中显示为白色 */
|
|
|
- filter: brightness(0) invert(1);
|
|
|
}
|
|
|
|
|
|
+ .default-icon {
|
|
|
+ filter: brightness(0) invert(1);
|
|
|
+ }
|
|
|
+
|
|
|
+ .real-icon {
|
|
|
+ width: 96rpx;
|
|
|
+ height: 96rpx;
|
|
|
+ border-radius: 22rpx;
|
|
|
+ box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
|
|
+ }
|
|
|
+
|
|
|
.tile-badge {
|
|
|
position: absolute;
|
|
|
bottom: 12rpx;
|