SarahXia0405 commited on
Commit
ecc7c65
·
verified ·
1 Parent(s): 15402d8

Update web/src/components/Message.tsx

Browse files
Files changed (1) hide show
  1. web/src/components/Message.tsx +30 -19
web/src/components/Message.tsx CHANGED
@@ -1,3 +1,4 @@
 
1
  import React, { useEffect, useMemo, useRef, useState } from "react";
2
  import { Button } from "./ui/button";
3
  import {
@@ -77,7 +78,7 @@ export function FileUploadArea({
77
  const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);
78
  const [showTypeDialog, setShowTypeDialog] = useState(false);
79
 
80
- // objectURL cache(ref 不触发渲染,所以 URL 必须在 render 时按需创建)
81
  const urlCacheRef = useRef<Map<string, string>>(new Map());
82
 
83
  const fingerprint = (f: File) => `${f.name}::${f.size}::${f.lastModified}`;
@@ -89,7 +90,7 @@ export function FileUploadArea({
89
  ];
90
  }, [uploadedFiles, pendingFiles]);
91
 
92
- // 当文件列表变化:revoke 掉不再需要的 url
93
  useEffect(() => {
94
  const need = new Set<string>();
95
  for (const f of allFiles) {
@@ -97,6 +98,7 @@ export function FileUploadArea({
97
  need.add(fingerprint(f));
98
  }
99
 
 
100
  for (const [key, url] of urlCacheRef.current.entries()) {
101
  if (!need.has(key)) {
102
  try {
@@ -107,9 +109,18 @@ export function FileUploadArea({
107
  urlCacheRef.current.delete(key);
108
  }
109
  }
 
 
 
 
 
 
 
 
 
110
  }, [allFiles]);
111
 
112
- // 卸载:全部 revoke
113
  useEffect(() => {
114
  return () => {
115
  for (const url of urlCacheRef.current.values()) {
@@ -123,15 +134,9 @@ export function FileUploadArea({
123
  };
124
  }, []);
125
 
126
- // 关键:render 时同步 getOrCreate,这样第一次渲染就能拿到 src
127
- const getOrCreatePreviewUrl = (file: File) => {
128
  const key = fingerprint(file);
129
- const existed = urlCacheRef.current.get(key);
130
- if (existed) return existed;
131
-
132
- const url = URL.createObjectURL(file);
133
- urlCacheRef.current.set(key, url);
134
- return url;
135
  };
136
 
137
  const filterSupportedFiles = (files: File[]) => {
@@ -195,15 +200,21 @@ export function FileUploadArea({
195
 
196
  const renderLeading = (file: File) => {
197
  if (isImageFile(file)) {
198
- const src = getOrCreatePreviewUrl(file);
199
  return (
200
  <div className="h-12 w-12 rounded-md overflow-hidden bg-background border border-border flex-shrink-0">
201
- <img
202
- src={src}
203
- alt={file.name}
204
- className="h-full w-full object-cover"
205
- draggable={false}
206
- />
 
 
 
 
 
 
207
  </div>
208
  );
209
  }
@@ -359,4 +370,4 @@ export function FileUploadArea({
359
  )}
360
  </Card>
361
  );
362
- }
 
1
+ 确实是看不到你看看这里web/src/components/Message.tsx呢:
2
  import React, { useEffect, useMemo, useRef, useState } from "react";
3
  import { Button } from "./ui/button";
4
  import {
 
78
  const [pendingFiles, setPendingFiles] = useState<PendingFile[]>([]);
79
  const [showTypeDialog, setShowTypeDialog] = useState(false);
80
 
81
+ // ===== objectURL cache(更稳:不用 state,避免时序问题)=====
82
  const urlCacheRef = useRef<Map<string, string>>(new Map());
83
 
84
  const fingerprint = (f: File) => `${f.name}::${f.size}::${f.lastModified}`;
 
90
  ];
91
  }, [uploadedFiles, pendingFiles]);
92
 
93
+ // 维护 cache:只保留当前需要的 image url;移除的立即 revoke
94
  useEffect(() => {
95
  const need = new Set<string>();
96
  for (const f of allFiles) {
 
98
  need.add(fingerprint(f));
99
  }
100
 
101
+ // revoke removed
102
  for (const [key, url] of urlCacheRef.current.entries()) {
103
  if (!need.has(key)) {
104
  try {
 
109
  urlCacheRef.current.delete(key);
110
  }
111
  }
112
+
113
+ // create missing
114
+ for (const f of allFiles) {
115
+ if (!isImageFile(f)) continue;
116
+ const key = fingerprint(f);
117
+ if (!urlCacheRef.current.has(key)) {
118
+ urlCacheRef.current.set(key, URL.createObjectURL(f));
119
+ }
120
+ }
121
  }, [allFiles]);
122
 
123
+ // unmount:全部 revoke
124
  useEffect(() => {
125
  return () => {
126
  for (const url of urlCacheRef.current.values()) {
 
134
  };
135
  }, []);
136
 
137
+ const getPreviewUrl = (file: File) => {
 
138
  const key = fingerprint(file);
139
+ return urlCacheRef.current.get(key);
 
 
 
 
 
140
  };
141
 
142
  const filterSupportedFiles = (files: File[]) => {
 
200
 
201
  const renderLeading = (file: File) => {
202
  if (isImageFile(file)) {
203
+ const src = getPreviewUrl(file);
204
  return (
205
  <div className="h-12 w-12 rounded-md overflow-hidden bg-background border border-border flex-shrink-0">
206
+ {src ? (
207
+ <img
208
+ src={src}
209
+ alt={file.name}
210
+ className="h-full w-full object-cover"
211
+ draggable={false}
212
+ />
213
+ ) : (
214
+ <div className="h-full w-full flex items-center justify-center text-muted-foreground">
215
+ <ImageIcon className="h-5 w-5" />
216
+ </div>
217
+ )}
218
  </div>
219
  );
220
  }
 
370
  )}
371
  </Card>
372
  );
373
+ }