reactjs - Next.js '找不到模块'./filename.jpeg'。来不及上传图片
问题描述
试图解决这个问题至少半周。我正在尝试创建播放列表页面。
首先,我使用getServerSideProps获取有关播放列表的信息并将其发送到该州。然后我会改变这个状态,直到用户离开页面。State 是对象数组,其中 object 是有关播放列表的信息。每个对象都通过 props 发送到特殊的播放列表组件。
如果用户想要创建新的播放列表,他打开模式窗口并设置信息(名称和描述)并为其选择自己的图片(或者用户可以保留默认图片)。提交后将这些信息发送到数据库,通过sharp.js修复图片,然后通过multer保存为服务器上的文件。然后模式窗口将关闭,用户应该会看到更新的播放列表列表。
播放列表组件包括下一个/图像组件。在 src 我通过(requrie(.../${avatar})
. 在第一次渲染或使用默认图片创建新播放列表后,我的构建工作完美。但是如果用户上传自己的图片,那么(提交后)页面会立即中断并出现错误“找不到模块./filename.jpeg”,然后在 2-3 秒内错误消失并且用户只有白屏(直到完全重新加载)。
页面播放列表.tsx
const Playlists: React.FC<PlaylistsProps> = ({ user, playlists }) => {
const [playlistList, setPlaylistList] = React.useState(playlists);
const [isModalActive, setIsModalActive] = React.useState(false);
const handleAddPlaylistClick = () => {
setIsModalActive(true);
};
const handleUploadedClick = () => {
alert("Uploaded playlist");
};
return (
<>
<div
className={clsx({
[styles.mask]: isModalActive,
})}
/>
<PlaylistModal
active={isModalActive}
modalClose={setIsModalActive}
setPlaylistList={setPlaylistList}
/>
<main className={styles.wrapper}>
<div className={styles.main}>
<Header name={user.userName!} avatar={user.avatarUrl!} />
<Aside />
<div className={styles.title}>
<div className={styles.picture}>
<Image
src="/logo/logo-love-1000.png"
width={150}
height={150}
alt="logo"
/>
</div>
<div className={styles.text}>
<span>Playlists</span>
</div>
</div>
<section className={styles.playlists_wrapper}>
<ul className={styles.playlists}>
<li
onClick={handleAddPlaylistClick}
className={clsx(styles.playlist, styles.compulsory)}
>
<svg
viewBox="0 0 512 512"
className={clsx(styles.avatar, styles.svg)}
>
<path d="m256 512c-141.164062 0-256-114.835938-256-256s114.835938-256 256-256 256 114.835938 256 256-114.835938 256-256 256zm0-480c-123.519531 0-224 100.480469-224 224s100.480469 224 224 224 224-100.480469 224-224-100.480469-224-224-224zm0 0" />
<path d="m368 272h-224c-8.832031 0-16-7.167969-16-16s7.167969-16 16-16h224c8.832031 0 16 7.167969 16 16s-7.167969 16-16 16zm0 0" />
<path d="m256 384c-8.832031 0-16-7.167969-16-16v-224c0-8.832031 7.167969-16 16-16s16 7.167969 16 16v224c0 8.832031-7.167969 16-16 16zm0 0" />
</svg>
<span className={styles.name}>Add new playlist</span>
</li>
<li
onClick={handleUploadedClick}
className={clsx(styles.playlist, styles.compulsory)}
>
<svg
className={clsx(styles.svg, styles.avatar)}
version="1.1"
viewBox="0 0 490.667 490.667"
>
<path
d="M245.333,0C110.059,0,0,110.059,0,245.333s110.059,245.333,245.333,245.333s245.333-110.059,245.333-245.333
S380.608,0,245.333,0z M245.333,469.333c-123.52,0-224-100.48-224-224s100.48-224,224-224s224,100.48,224,224
S368.853,469.333,245.333,469.333z"
/>
<path
d="M245.333,106.667c-5.888,0-10.667,4.779-10.667,10.667v256c0,5.888,4.779,10.667,10.667,10.667S256,379.221,256,373.333
v-256C256,111.445,251.221,106.667,245.333,106.667z"
/>
<path
d="M338.219,195.115l-85.333-85.333c-4.16-4.16-10.923-4.16-15.083,0l-85.333,85.333c-4.16,4.16-4.16,10.923,0,15.083
c4.16,4.16,10.923,4.16,15.083,0l77.781-77.781l77.781,77.803c2.091,2.069,4.821,3.115,7.552,3.115
c2.731,0,5.461-1.045,7.552-3.136C342.379,206.037,342.379,199.275,338.219,195.115z"
/>
</svg>
<span className={styles.name}>Uploaded songs</span>
</li>
{playlistList.map((obj, id: number) => (
<Playlist key={id} name={obj.name} avatar={obj.avatarUrl} />
))}
</ul>
</section>
</div>
</main>
</>
);
};
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
try {
const user = await checkAuth(ctx);
if (!user) {
return {
props: {},
redirect: {
permanent: false,
destination: "/auth/login",
},
};
}
if (user.genrePreferences?.length == 0) {
return {
props: {},
redirect: {
permanent: false,
destination: "/welcome",
},
};
}
const playlists = await Api(ctx).getPlaylists();
return {
props: {
playlists,
user,
},
};
} catch (error) {
console.warn(error);
}
};
PlaylistModal.tsx(模态窗口)
const PlaylistModal: React.FC<PlaylistModalProps> = ({
active,
modalClose,
setPlaylistList,
}) => {
const [imageUrl, setImageUrl] = React.useState<string>("");
const [imageFile, setImageFile] = React.useState<File>();
const [playlistInfo, setPlayListInfo] = React.useState<PlaylistInfo>({
name: "",
description: "",
});
const sendInfo = async () => {
try {
const formData = new FormData();
if (imageFile) {
formData.append("avatar", imageFile);
} else {
formData.append("avatar", "");
}
formData.append("name", playlistInfo.name);
formData.append("description", playlistInfo.description);
const result = await Api().createPlaylist(formData);
return result;
} catch (error) {
console.log(error);
}
};
const handleInfoChange = (e: React.ChangeEvent) => {
setPlayListInfo({
...playlistInfo,
[e.target.name]: e.target.value,
});
};
const handleImageChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const target = event.target as HTMLInputElement;
if (target.files) {
const file = target.files[0];
if (file) {
const imageUrl = URL.createObjectURL(file);
setImageUrl(imageUrl);
setImageFile(file);
target.value = "";
}
}
};
const handleSubmitClick = async () => {
try {
const newPlaylist = await sendInfo();
setPlayListInfo({
name: "",
description: "",
});
setImageFile(undefined);
setImageUrl("");
modalClose(false);
setPlaylistList((prevState) => [...prevState, newPlaylist]);
} catch (error) {
console.log(error);
}
};
return (
<div
className={clsx(styles.modal_wrapper, {
[styles.modal_active]: active,
})}
>
<div className={styles.modal}>
<svg
onClick={() => modalClose(false)}
className={styles.close}
width="30pt"
height="30pt"
viewBox="0 0 511.995 511.995"
>
<path
d="M437.126,74.939c-99.826-99.826-262.307-99.826-362.133,0C26.637,123.314,0,187.617,0,256.005
s26.637,132.691,74.993,181.047c49.923,49.923,115.495,74.874,181.066,74.874s131.144-24.951,181.066-74.874
C536.951,337.226,536.951,174.784,437.126,74.939z M409.08,409.006c-84.375,84.375-221.667,84.375-306.042,0
c-40.858-40.858-63.37-95.204-63.37-153.001s22.512-112.143,63.37-153.021c84.375-84.375,221.667-84.355,306.042,0
C493.435,187.359,493.435,324.651,409.08,409.006z"
/>
<path
d="M341.525,310.827l-56.151-56.071l56.151-56.071c7.735-7.735,7.735-20.29,0.02-28.046
c-7.755-7.775-20.31-7.755-28.065-0.02l-56.19,56.111l-56.19-56.111c-7.755-7.735-20.31-7.755-28.065,0.02
c-7.735,7.755-7.735,20.31,0.02,28.046l56.151,56.071l-56.151,56.071c-7.755,7.735-7.755,20.29-0.02,28.046
c3.868,3.887,8.965,5.811,14.043,5.811s10.155-1.944,14.023-5.792l56.19-56.111l56.19,56.111
c3.868,3.868,8.945,5.792,14.023,5.792c5.078,0,10.175-1.944,14.043-5.811C349.28,331.117,349.28,318.562,341.525,310.827z"
/>
</svg>
<div className={styles.title}>
<img
src="/logo/logo-happy-1000.png"
className={styles.pic}
alt="logo"
/>
<span className={styles.text}>Create the playlist</span>
</div>
<form className={styles.form}>
<div className={styles.info}>
<label htmlFor="upload" className={styles.avatar}>
<div className={styles.picture}>
<img
width={300}
height={300}
className={styles.image}
src={
imageUrl != ""
? imageUrl
: "/defaults/playlist-default.jpeg"
}
alt="avatar"
/>
</div>
<div className={styles.text}>
<span> Choose the avatar</span>
</div>
</label>
<input
onChange={handleImageChange}
id="upload"
className={styles.upload}
type="file"
name="avatar"
/>
<div className={styles.input_info}>
<div className={styles.input_block}>
<span className={styles.input_title}>
Enter the name of your new playlist:
</span>
<input
name="name"
placeholder="Name..."
className={clsx(styles.input, styles.input_name)}
type="text"
value={playlistInfo.name}
onChange={handleInfoChange}
></input>
</div>
<div className={styles.input_block}>
<span className={styles.input_title}>
Enter the description of your new playlist:
</span>
<textarea
name="description"
value={playlistInfo.description}
placeholder="Description..."
className={clsx(styles.input, styles.descr)}
onChange={handleInfoChange}
></textarea>
</div>
</div>
</div>
<Button
onClick={handleSubmitClick}
color={["white", "#a406cb", "none"]}
size={[200, 50]}
className={styles.submit}
>
Lets go
</Button>
</form>
</div>
</div>
);
};
export default PlaylistModal;
组件播放列表.tsx
const Playlist: React.FC<PlaylistProps> = ({ name, avatar }) => {
const handlePlaylistClick = (e: React.MouseEvent<HTMLLIElement>) => {
if (e.target.tagName !== "svg" && e.target.tagName !== "path") {
alert(`Playlist named '${name}'`);
}
};
const handleEditClick = () => {
alert(`Edit '${name}'`);
};
const handleDeleteClick = (e: React.MouseEvent<SVGSVGElement>) => {
alert(`Delete '${name}'`);
};
return (
<li onClick={handlePlaylistClick} className={styles.playlist}>
<Image
className={styles.avatar}
width={50}
height={50}
src={require(`/server/avatars/playlists/${avatar}`)}
alt="playlist-avatar"
/>
<span className={styles.name}>{name}</span>
<div className={styles.tools}>
<svg
onClick={handleEditClick}
width="30pt"
height="30pt"
className={styles.tool}
viewBox="-15 -15 484.00019 484"
>
<path d="m401.648438 18.234375c-24.394532-24.351563-63.898438-24.351563-88.292969 0l-22.101563 22.222656-235.269531 235.144531-.5.503907c-.121094.121093-.121094.25-.25.25-.25.375-.625.746093-.871094 1.121093 0 .125-.128906.125-.128906.25-.25.375-.371094.625-.625 1-.121094.125-.121094.246094-.246094.375-.125.375-.25.625-.378906 1 0 .121094-.121094.121094-.121094.25l-52.199219 156.96875c-1.53125 4.46875-.367187 9.417969 2.996094 12.734376 2.363282 2.332031 5.550782 3.636718 8.867188 3.625 1.355468-.023438 2.699218-.234376 3.996094-.625l156.847656-52.324219c.121094 0 .121094 0 .25-.121094.394531-.117187.773437-.285156 1.121094-.503906.097656-.011719.183593-.054688.253906-.121094.371094-.25.871094-.503906 1.246094-.753906.371093-.246094.75-.621094 1.125-.871094.125-.128906.246093-.128906.246093-.25.128907-.125.378907-.246094.503907-.5l257.371093-257.371094c24.351563-24.394531 24.351563-63.898437 0-88.289062zm-232.273438 353.148437-86.914062-86.910156 217.535156-217.535156 86.914062 86.910156zm-99.15625-63.808593 75.929688 75.925781-114.015626 37.960938zm347.664062-184.820313-13.238281 13.363282-86.917969-86.917969 13.367188-13.359375c14.621094-14.609375 38.320312-14.609375 52.945312 0l33.964844 33.964844c14.511719 14.6875 14.457032 38.332031-.121094 52.949218zm0 0" />
</svg>
<svg
onClick={handleDeleteClick}
width="30pt"
height="30pt"
className={styles.tool}
viewBox="-40 0 427 427.00131"
>
<path d="m232.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" />
<path d="m114.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" />
<path d="m28.398438 127.121094v246.378906c0 14.5625 5.339843 28.238281 14.667968 38.050781 9.285156 9.839844 22.207032 15.425781 35.730469 15.449219h189.203125c13.527344-.023438 26.449219-5.609375 35.730469-15.449219 9.328125-9.8125 14.667969-23.488281 14.667969-38.050781v-246.378906c18.542968-4.921875 30.558593-22.835938 28.078124-41.863282-2.484374-19.023437-18.691406-33.253906-37.878906-33.257812h-51.199218v-12.5c.058593-10.511719-4.097657-20.605469-11.539063-28.03125-7.441406-7.421875-17.550781-11.5546875-28.0625-11.46875h-88.796875c-10.511719-.0859375-20.621094 4.046875-28.0625 11.46875-7.441406 7.425781-11.597656 17.519531-11.539062 28.03125v12.5h-51.199219c-19.1875.003906-35.394531 14.234375-37.878907 33.257812-2.480468 19.027344 9.535157 36.941407 28.078126 41.863282zm239.601562 279.878906h-189.203125c-17.097656 0-30.398437-14.6875-30.398437-33.5v-245.5h250v245.5c0 18.8125-13.300782 33.5-30.398438 33.5zm-158.601562-367.5c-.066407-5.207031 1.980468-10.21875 5.675781-13.894531 3.691406-3.675781 8.714843-5.695313 13.925781-5.605469h88.796875c5.210937-.089844 10.234375 1.929688 13.925781 5.605469 3.695313 3.671875 5.742188 8.6875 5.675782 13.894531v12.5h-128zm-71.199219 32.5h270.398437c9.941406 0 18 8.058594 18 18s-8.058594 18-18 18h-270.398437c-9.941407 0-18-8.058594-18-18s8.058593-18 18-18zm0 0" />
<path d="m173.398438 154.703125c-5.523438 0-10 4.476563-10 10v189c0 5.519531 4.476562 10 10 10 5.523437 0 10-4.480469 10-10v-189c0-5.523437-4.476563-10-10-10zm0 0" />
</svg>
</div>
</li>
);
};
export default Playlist;
向服务器请求
createPlaylist: async(_info: FormData) => {
try {
const { data } = await instance.post("/playlists/create", _info, {
headers: {
"Content-Type": "multipart/form-data",
}
}
);
return data;
} catch (error) {
console.log(error);
}
},
处理信息并发送到数据库
async createPlaylist(req: express.Request, res: express.Response) {
if (req.file) {
try {
const { id } = req.user!.data;
const { filename: image } = req.file
const filePath = req.file?.path;
let newFileName: string;
newFileName = image;
await sharp(path.resolve(filePath)).resize(150, 150).toFormat('jpeg').toFile(path.resolve(req.file?.destination, "playlists", newFileName)), (err: any) => {
if (err) {
throw err;
}
}
fs.unlinkSync(filePath);
const obj = {
name: req.body.name,
description: req.body.description,
avatarUrl: newFileName,
songs: [],
private: false,
belongsTo: id,
}
const playlistInfo = await Playlist.create(obj);
res.status(200).json(playlistInfo);
} catch (error) {
console.log(error);
res.sendStatus(500);
}
} else {
try {
const { id } = req.user!.data;
const obj = {
name: req.body.name,
description: req.body.description,
avatarUrl: "default.jpeg",
songs: [],
private: false,
belongsTo: id,
};
const playlistInfo = await Playlist.create(obj);
res.status(200).json(await playlistInfo.toJSON())
} catch (error) {
console.log(error);
res.sendStatus(500);
}
}
}
我想,出现这个错误是因为用户的图片没有足够的时间上传到服务器文件夹上,所以next/image组件实际上需要不存在的图片。我怎样才能等待服务器上的图像上传,然后才更新页面?如果这不是问题,那会是什么?
解决方案
最后,我解决了这个问题,将“/server/avatars/playlists”文件夹中的所有头像替换为“public/avatars/playlists”文件夹并更改播放列表组件中的src
<Image
className={styles.avatar}
width={50}
height={50}
src={`/avatars/playlists/${avatar}`}
alt="playlist-avatar"
/>
推荐阅读
- node.js - 将猫鼬连接导出到我的 model.js 文件
- coldfusion - 基于数组模数创建四种结构的coldfusion不那么繁琐的方法
- javascript - 在某个位置停止 2 个滑块
- javascript - ReactJs 菜单链接堆叠主要部分组件
- c# - 创建 Windows 服务时出现 SchedularCallBack 错误
- c# - 使用盒子碰撞器添加扭矩枢轴点
- mysql - 检查表b中是否存在表a主键
- visual-studio-code - 在 Visual Studio Code 中禁用基于单词的建议
- javascript - 在wordpress中集成一个js
- memory-management - 单分区分配中的瞬态操作系统代码?