|
|
@@ -26,8 +26,14 @@
|
|
|
<el-input spellcheck="false" v-model="imageSearch" size="small" placeholder="输入搜索..." />
|
|
|
</div>
|
|
|
<el-scrollbar class="flex-1">
|
|
|
- <ResourceItem v-for="item in getImages || []" :key="item.id" :data="item" type="image"
|
|
|
- @delete="deleteResource(item, 'images')" />
|
|
|
+ <ResourceItem
|
|
|
+ v-for="item in getImages || []"
|
|
|
+ :key="item.id"
|
|
|
+ :data="item"
|
|
|
+ type="image"
|
|
|
+ :image-use-count="imageUseCountMap[item.id] || 0"
|
|
|
+ @delete="deleteResource(item, 'images')"
|
|
|
+ />
|
|
|
<div v-if="!getImages?.length" class="text-center text-text-secondary">暂无图片~</div>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
@@ -75,8 +81,11 @@
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import type { FontResource, ImageResource, OtherResource, Resource } from '@/types/resource'
|
|
|
+import type { BaseWidget } from '@/types/baseWidget'
|
|
|
+import type { Page } from '@/types/page'
|
|
|
+import type { Screen } from '@/types/screen'
|
|
|
|
|
|
-import { ref, computed } from 'vue'
|
|
|
+import { computed, onBeforeUnmount, shallowRef, watch, ref } from 'vue'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import { SplitterCollapse, SplitterCollapseItem } from '@/components/SplitterCollapse'
|
|
|
import { LuPlus, LuTrash2 } from 'vue-icons-plus/lu'
|
|
|
@@ -163,6 +172,131 @@ const getOthers = computed(() => {
|
|
|
})
|
|
|
|
|
|
// 添加图片
|
|
|
+const countImageRefs = (
|
|
|
+ value: unknown,
|
|
|
+ imageIds: Set<string>,
|
|
|
+ useCount: Record<string, number>
|
|
|
+) => {
|
|
|
+ if (!value) return
|
|
|
+
|
|
|
+ if (typeof value === 'string') {
|
|
|
+ if (imageIds.has(value)) {
|
|
|
+ useCount[value] = (useCount[value] || 0) + 1
|
|
|
+ }
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Array.isArray(value)) {
|
|
|
+ value.forEach((item) => countImageRefs(item, imageIds, useCount))
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof value === 'object') {
|
|
|
+ Object.values(value as Record<string, unknown>).forEach((item) =>
|
|
|
+ countImageRefs(item, imageIds, useCount)
|
|
|
+ )
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const countWidgetImageRefs = (
|
|
|
+ widget: BaseWidget,
|
|
|
+ imageIds: Set<string>,
|
|
|
+ useCount: Record<string, number>
|
|
|
+) => {
|
|
|
+ countImageRefs(widget.props, imageIds, useCount)
|
|
|
+ countImageRefs(widget.style, imageIds, useCount)
|
|
|
+ countImageRefs(widget.events, imageIds, useCount)
|
|
|
+ widget.children?.forEach((child) => countWidgetImageRefs(child, imageIds, useCount))
|
|
|
+}
|
|
|
+
|
|
|
+const countPageImageRefs = (
|
|
|
+ page: Page,
|
|
|
+ imageIds: Set<string>,
|
|
|
+ useCount: Record<string, number>
|
|
|
+) => {
|
|
|
+ countImageRefs(page.props, imageIds, useCount)
|
|
|
+ countImageRefs(page.style, imageIds, useCount)
|
|
|
+ countImageRefs(page.events, imageIds, useCount)
|
|
|
+ page.children?.forEach((widget) => countWidgetImageRefs(widget, imageIds, useCount))
|
|
|
+}
|
|
|
+
|
|
|
+const imageUseCountMap = shallowRef<Record<string, number>>({})
|
|
|
+
|
|
|
+type IdleWindow = Window & {
|
|
|
+ requestIdleCallback?: (callback: IdleRequestCallback, options?: IdleRequestOptions) => number
|
|
|
+ cancelIdleCallback?: (handle: number) => void
|
|
|
+}
|
|
|
+
|
|
|
+let refreshTimer: ReturnType<typeof setTimeout> | undefined
|
|
|
+let refreshIdleId: number | undefined
|
|
|
+
|
|
|
+const clearScheduledUseCountRefresh = () => {
|
|
|
+ if (refreshTimer !== undefined) {
|
|
|
+ clearTimeout(refreshTimer)
|
|
|
+ refreshTimer = undefined
|
|
|
+ }
|
|
|
+
|
|
|
+ if (refreshIdleId !== undefined) {
|
|
|
+ ;(window as IdleWindow).cancelIdleCallback?.(refreshIdleId)
|
|
|
+ refreshIdleId = undefined
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const refreshImageUseCount = () => {
|
|
|
+ const images = projectStore.project?.resources.images || []
|
|
|
+ const screens = projectStore.project?.screens || []
|
|
|
+ const imageIds = new Set(images.map((item) => item.id))
|
|
|
+ const useCount: Record<string, number> = {}
|
|
|
+
|
|
|
+ if (!imageIds.size) {
|
|
|
+ imageUseCountMap.value = useCount
|
|
|
+ return useCount
|
|
|
+ }
|
|
|
+
|
|
|
+ screens.forEach((screen: Screen) => {
|
|
|
+ screen.pages.forEach((page) => countPageImageRefs(page, imageIds, useCount))
|
|
|
+ })
|
|
|
+
|
|
|
+ imageUseCountMap.value = useCount
|
|
|
+ return useCount
|
|
|
+}
|
|
|
+
|
|
|
+const scheduleImageUseCountRefresh = () => {
|
|
|
+ clearScheduledUseCountRefresh()
|
|
|
+
|
|
|
+ refreshTimer = setTimeout(() => {
|
|
|
+ refreshTimer = undefined
|
|
|
+ const idleWindow = window as IdleWindow
|
|
|
+
|
|
|
+ if (idleWindow.requestIdleCallback) {
|
|
|
+ refreshIdleId = idleWindow.requestIdleCallback(
|
|
|
+ () => {
|
|
|
+ refreshIdleId = undefined
|
|
|
+ refreshImageUseCount()
|
|
|
+ },
|
|
|
+ { timeout: 1500 }
|
|
|
+ )
|
|
|
+ } else {
|
|
|
+ refreshImageUseCount()
|
|
|
+ }
|
|
|
+ }, 300)
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [
|
|
|
+ projectStore.projectRevision,
|
|
|
+ projectStore.project?.resources.images.map((item) => item.id).join('|')
|
|
|
+ ],
|
|
|
+ scheduleImageUseCountRefresh,
|
|
|
+ {
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ clearScheduledUseCountRefresh()
|
|
|
+})
|
|
|
+
|
|
|
const handleAddImage = async () => {
|
|
|
const paths = await window.electron.ipcRenderer.invoke('get-file', {
|
|
|
title: '选择文件',
|
|
|
@@ -219,10 +353,9 @@ const deleteResource = async (resource: Resource, type: 'images' | 'fonts' | 'ot
|
|
|
|
|
|
// 清除未使用图片
|
|
|
const handleClearUnusedImage = () => {
|
|
|
- projectStore.project?.resources.images.forEach((item) => {
|
|
|
- const str = JSON.stringify(projectStore.project?.screens ?? [])
|
|
|
- const count = str.split(item.id).length - 1
|
|
|
- if (count === 0) {
|
|
|
+ const useCount = refreshImageUseCount()
|
|
|
+ projectStore.project?.resources.images.slice().forEach((item) => {
|
|
|
+ if (!useCount[item.id]) {
|
|
|
deleteResource(item, 'images')
|
|
|
}
|
|
|
})
|