9
9
Settings ,
10
10
Trash2 ,
11
11
} from ' lucide-vue-next'
12
+ import { storeToRefs } from ' pinia'
13
+ import { onBeforeUnmount , onMounted , ref , watch } from ' vue'
12
14
import { Button } from ' @/components/ui/button'
13
15
import {
14
16
Dialog ,
@@ -17,7 +19,7 @@ import {
17
19
DialogTitle ,
18
20
} from ' @/components/ui/dialog'
19
21
import { Textarea } from ' @/components/ui/textarea'
20
- import { useStore } from ' @/stores'
22
+ import { useDisplayStore , useStore } from ' @/stores'
21
23
import useAIImageConfigStore from ' @/stores/AIImageConfig'
22
24
import { copyPlain } from ' @/utils/clipboard'
23
25
import AIImageConfig from ' ./AIImageConfig.vue'
@@ -34,7 +36,13 @@ const { toggleAIDialog } = displayStore
34
36
35
37
/* ---------- 弹窗开关 ---------- */
36
38
const dialogVisible = ref (props .open )
37
- watch (() => props .open , val => (dialogVisible .value = val ))
39
+ watch (() => props .open , (val ) => {
40
+ dialogVisible .value = val
41
+ // 每次打开面板时检查并清理过期图片
42
+ if (val ) {
43
+ cleanExpiredImages ()
44
+ }
45
+ })
38
46
watch (dialogVisible , val => emit (` update:open ` , val ))
39
47
40
48
/* ---------- 状态管理 ---------- */
@@ -43,38 +51,131 @@ const loading = ref(false)
43
51
const prompt = ref <string >(` ` )
44
52
const generatedImages = ref <string []>([])
45
53
const imagePrompts = ref <string []>([]) // 存储每张图片对应的prompt
54
+ const imageTimestamps = ref <number []>([]) // 存储每张图片的生成时间戳
46
55
const abortController = ref <AbortController | null >(null )
47
56
const currentImageIndex = ref (0 )
57
+ const timeUpdateInterval = ref <NodeJS .Timeout | null >(null )
48
58
49
59
/* ---------- AI 配置 ---------- */
50
60
const AIImageConfigStore = useAIImageConfigStore ()
51
61
const { apiKey, endpoint, model, type, size, quality, style } = storeToRefs (AIImageConfigStore )
52
62
53
- /* ---------- 初始数据 ---------- */
54
- onMounted (() => {
63
+ /* ---------- 过期检查函数 ---------- */
64
+ function isImageExpired(timestamp : number ): boolean {
65
+ const EXPIRY_TIME = 60 * 60 * 1000 // 1小时,单位毫秒
66
+ const now = Date .now ()
67
+ return now - timestamp > EXPIRY_TIME
68
+ }
69
+
70
+ function cleanExpiredImages() {
55
71
const savedImages = localStorage .getItem (` ai_generated_images ` )
56
72
const savedPrompts = localStorage .getItem (` ai_image_prompts ` )
57
- if (savedImages ) {
58
- generatedImages .value = JSON .parse (savedImages )
73
+ const savedTimestamps = localStorage .getItem (` ai_image_timestamps ` )
74
+
75
+ if (! savedImages ) {
76
+ return
59
77
}
60
- if (savedPrompts ) {
61
- imagePrompts .value = JSON .parse (savedPrompts )
78
+
79
+ const images = JSON .parse (savedImages )
80
+ const prompts = savedPrompts ? JSON .parse (savedPrompts ) : []
81
+ const timestamps = savedTimestamps ? JSON .parse (savedTimestamps ) : []
82
+
83
+ // 如果没有时间戳数据,说明是旧版本,默认清除所有数据
84
+ if (! savedTimestamps || timestamps .length === 0 ) {
85
+ console .log (` 🧹 检测到旧版本数据,清除所有过期图片 ` )
86
+ generatedImages .value = []
87
+ imagePrompts .value = []
88
+ imageTimestamps .value = []
89
+ localStorage .removeItem (` ai_generated_images ` )
90
+ localStorage .removeItem (` ai_image_prompts ` )
91
+ localStorage .removeItem (` ai_image_timestamps ` )
92
+ return
93
+ }
94
+
95
+ // 过滤掉过期的图片
96
+ const validIndices: number [] = []
97
+ timestamps .forEach ((timestamp : number , index : number ) => {
98
+ if (! isImageExpired (timestamp )) {
99
+ validIndices .push (index )
100
+ }
101
+ })
102
+
103
+ const validImages = validIndices .map (i => images [i ]).filter (Boolean )
104
+ const validPrompts = validIndices .map (i => prompts [i ] || ` ` ).filter ((_ , index ) => validImages [index ])
105
+ const validTimestamps = validIndices .map (i => timestamps [i ]).filter (Boolean )
106
+
107
+ // 更新数据
108
+ generatedImages .value = validImages
109
+ imagePrompts .value = validPrompts
110
+ imageTimestamps .value = validTimestamps
111
+
112
+ // 如果有数据被清除,更新localStorage
113
+ if (validImages .length < images .length ) {
114
+ console .log (` 🧹 清除了 ${images .length - validImages .length } 张过期图片 ` )
115
+ if (validImages .length > 0 ) {
116
+ localStorage .setItem (` ai_generated_images ` , JSON .stringify (validImages ))
117
+ localStorage .setItem (` ai_image_prompts ` , JSON .stringify (validPrompts ))
118
+ localStorage .setItem (` ai_image_timestamps ` , JSON .stringify (validTimestamps ))
119
+ }
120
+ else {
121
+ localStorage .removeItem (` ai_generated_images ` )
122
+ localStorage .removeItem (` ai_image_prompts ` )
123
+ localStorage .removeItem (` ai_image_timestamps ` )
124
+ }
62
125
}
63
126
127
+ console .log (` 📊 过期检查完成,有效图片数量: ` , validImages .length )
128
+ }
129
+
130
+ /* ---------- 初始数据 ---------- */
131
+ onMounted (() => {
132
+ // 先进行过期检查和清理
133
+ cleanExpiredImages ()
134
+
64
135
// 确保数组长度一致
65
136
const imagesLength = generatedImages .value .length
66
137
const promptsLength = imagePrompts .value .length
67
-
68
- if (imagesLength > promptsLength ) {
69
- // 如果图片多于提示词,用空字符串填充
70
- imagePrompts .value = [... imagePrompts .value , ... Array .from ({ length: imagesLength - promptsLength }, () => ` ` )]
138
+ const timestampsLength = imageTimestamps .value .length
139
+
140
+ const maxLength = Math .max (imagesLength , promptsLength , timestampsLength )
141
+
142
+ if (imagesLength < maxLength ) {
143
+ // 如果图片少于其他数组,说明数据不一致,清除所有数据
144
+ console .warn (` ⚠️ 数据不一致,清除所有数据 ` )
145
+ generatedImages .value = []
146
+ imagePrompts .value = []
147
+ imageTimestamps .value = []
148
+ localStorage .removeItem (` ai_generated_images ` )
149
+ localStorage .removeItem (` ai_image_prompts ` )
150
+ localStorage .removeItem (` ai_image_timestamps ` )
71
151
}
72
- else if (promptsLength > imagesLength ) {
73
- // 如果提示词多于图片,截断提示词数组
74
- imagePrompts .value = imagePrompts .value .slice (0 , imagesLength )
152
+ else {
153
+ // 补齐较短的数组
154
+ if (promptsLength < imagesLength ) {
155
+ imagePrompts .value = [... imagePrompts .value , ... Array .from ({ length: imagesLength - promptsLength }, () => ` ` )]
156
+ }
157
+ if (timestampsLength < imagesLength ) {
158
+ imageTimestamps .value = [... imageTimestamps .value , ... Array .from ({ length: imagesLength - timestampsLength }, () => Date .now ())]
159
+ }
75
160
}
76
161
77
- console .log (` 📊 数据加载完成,图片数量: ` , imagesLength , ` 提示词数量: ` , imagePrompts .value .length )
162
+ console .log (` 📊 数据加载完成,图片数量: ` , generatedImages .value .length , ` 提示词数量: ` , imagePrompts .value .length , ` 时间戳数量: ` , imageTimestamps .value .length )
163
+
164
+ // 启动定时器,每30秒检查一次过期图片并更新时间显示
165
+ timeUpdateInterval .value = setInterval (() => {
166
+ // 检查并清理过期图片
167
+ if (generatedImages .value .length > 0 ) {
168
+ cleanExpiredImages ()
169
+ }
170
+ }, 30000 ) // 30秒
171
+ })
172
+
173
+ onBeforeUnmount (() => {
174
+ // 清除定时器
175
+ if (timeUpdateInterval .value ) {
176
+ clearInterval (timeUpdateInterval .value )
177
+ timeUpdateInterval .value = null
178
+ }
78
179
})
79
180
80
181
/* ---------- 事件处理 ---------- */
@@ -155,18 +256,23 @@ async function generateImage() {
155
256
? imageUrl
156
257
: ` data:image/png;base64,${imageUrl } `
157
258
259
+ const currentTimestamp = Date .now ()
260
+
158
261
generatedImages .value .unshift (finalUrl )
159
262
imagePrompts .value .unshift (prompt .value .trim ()) // 保存对应的prompt
263
+ imageTimestamps .value .unshift (currentTimestamp ) // 保存生成时间戳
160
264
currentImageIndex .value = 0
161
265
162
266
// 限制存储的图片数量,避免占用过多存储空间
163
267
if (generatedImages .value .length > 20 ) {
164
268
generatedImages .value = generatedImages .value .slice (0 , 20 )
165
269
imagePrompts .value = imagePrompts .value .slice (0 , 20 )
270
+ imageTimestamps .value = imageTimestamps .value .slice (0 , 20 )
166
271
}
167
272
168
273
localStorage .setItem (` ai_generated_images ` , JSON .stringify (generatedImages .value ))
169
274
localStorage .setItem (` ai_image_prompts ` , JSON .stringify (imagePrompts .value ))
275
+ localStorage .setItem (` ai_image_timestamps ` , JSON .stringify (imageTimestamps .value ))
170
276
}
171
277
}
172
278
else {
@@ -201,9 +307,11 @@ function cancelGeneration() {
201
307
function clearImages() {
202
308
generatedImages .value = []
203
309
imagePrompts .value = []
310
+ imageTimestamps .value = []
204
311
currentImageIndex .value = 0
205
312
localStorage .removeItem (` ai_generated_images ` )
206
313
localStorage .removeItem (` ai_image_prompts ` )
314
+ localStorage .removeItem (` ai_image_timestamps ` )
207
315
}
208
316
209
317
/* ---------- 下载图像 ---------- */
@@ -346,6 +454,73 @@ function viewFullImage(imageUrl: string) {
346
454
console .error (` ❌ 打开图片失败: ` , error )
347
455
}
348
456
}
457
+
458
+ /* ---------- 时间相关函数 ---------- */
459
+ const currentTime = ref (Date .now ())
460
+
461
+ // 每秒更新当前时间,用于实时显示剩余时间
462
+ onMounted (() => {
463
+ const updateTime = () => {
464
+ currentTime .value = Date .now ()
465
+ }
466
+
467
+ // 启动定时器更新时间显示
468
+ const timeDisplayInterval = setInterval (updateTime , 1000 )
469
+
470
+ // 组件卸载时清理定时器
471
+ onBeforeUnmount (() => {
472
+ clearInterval (timeDisplayInterval )
473
+ })
474
+ })
475
+
476
+ function getTimeRemaining(index : number ): string {
477
+ if (! imageTimestamps .value [index ]) {
478
+ return ` 未知 `
479
+ }
480
+
481
+ const EXPIRY_TIME = 60 * 60 * 1000 // 1小时
482
+ const timestamp = imageTimestamps .value [index ]
483
+ const elapsed = currentTime .value - timestamp
484
+ const remaining = EXPIRY_TIME - elapsed
485
+
486
+ if (remaining <= 0 ) {
487
+ return ` 已过期 `
488
+ }
489
+
490
+ const minutes = Math .floor (remaining / (60 * 1000 ))
491
+ const seconds = Math .floor ((remaining % (60 * 1000 )) / 1000 )
492
+
493
+ if (minutes > 0 ) {
494
+ return ` ${minutes }分${seconds }秒 `
495
+ }
496
+ else {
497
+ return ` ${seconds }秒 `
498
+ }
499
+ }
500
+
501
+ function getTimeRemainingClass(index : number ): string {
502
+ if (! imageTimestamps .value [index ]) {
503
+ return ` text-muted-foreground `
504
+ }
505
+
506
+ const EXPIRY_TIME = 60 * 60 * 1000 // 1小时
507
+ const timestamp = imageTimestamps .value [index ]
508
+ const elapsed = currentTime .value - timestamp
509
+ const remaining = EXPIRY_TIME - elapsed
510
+
511
+ if (remaining <= 0 ) {
512
+ return ` text-red-500 font-medium `
513
+ }
514
+ else if (remaining < 10 * 60 * 1000 ) { // 少于10分钟
515
+ return ` text-orange-500 font-medium `
516
+ }
517
+ else if (remaining < 30 * 60 * 1000 ) { // 少于30分钟
518
+ return ` text-yellow-600 `
519
+ }
520
+ else {
521
+ return ` text-green-600 `
522
+ }
523
+ }
349
524
</script >
350
525
351
526
<template >
@@ -473,6 +648,12 @@ function viewFullImage(imageUrl: string) {
473
648
<span class =" font-medium" >提示词:</span >
474
649
<span class =" ml-1" >{{ imagePrompts[currentImageIndex] || '无关联提示词' }}</span >
475
650
</div >
651
+ <div class =" text-xs text-muted-foreground text-center" >
652
+ <span class =" font-medium" >有效期:</span >
653
+ <span class =" ml-1" :class =" getTimeRemainingClass(currentImageIndex)" >
654
+ {{ getTimeRemaining(currentImageIndex) }}
655
+ </span >
656
+ </div >
476
657
</div >
477
658
478
659
<!-- 图像操作按钮 -->
0 commit comments