فهرست منبع

V1.1.7自定义图标

liuq 4 هفته پیش
والد
کامیت
c89e42dbda
2فایلهای تغییر یافته به همراه107 افزوده شده و 9 حذف شده
  1. 1 0
      .gitignore
  2. 106 9
      pages/app-center/index.vue

+ 1 - 0
.gitignore

@@ -18,6 +18,7 @@ unpackage/
 .hbuilderx/
 .idea/
 .vscode/
+.claude/
 
 # OS files
 .DS_Store

+ 106 - 9
pages/app-center/index.vue

@@ -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;