KeystoneJS 6+NextJS 13本地存储图片上传问题求助
应用架构
- 后端:Keystone服务器
- 前端:Next.js应用
- 使用Apollo Client实现GraphQL功能
这是一个电商商城,用户可在NextJS应用中完成所有CRUD操作。
问题描述
通过NextJS应用将图片上传至Keystone的本地存储时遇到困难。
我的代码
CreateProduct.tsx
import useForm from "@/lib/useForm" import { gql, useMutation } from "@apollo/client" import ErrorMessage from "../ErrorMessage" export const ProductCreate = () => { const {inputs, handleChange, clearForm, resetForm} = useForm({ image: '', name: 'new product', price: 123, }) const [createProduct, {loading, error, data}] = useMutation( CREATE_PRODUCT_MUTATION, {variables: {data: { photo: { create: { altText: `${inputs.name} featured image`, image: {upload: inputs.image} //** 错误位置 }, }, name: inputs.name, price: inputs.price, description: inputs.description, }}} ) async function handleSubmit(e: any) { e.preventDefault() console.log(inputs); const res = await createProduct() console.log(res); } return ( <form onSubmit={e => handleSubmit(e)}> <label htmlFor="image"> Image <input required type="file" id="image" name="image" onChange={handleChange} /> </label> ...折叠内容 </form> ) } const CREATE_PRODUCT_MUTATION = gql` mutation CreateProduct($data: ProductCreateInput!) { createProduct(data: $data) { name photo { altText id image { extension filesize height id url width } } id description price status } } `
错误信息
Unhandled Runtime Error ApolloError: Variable "$data" got invalid value {} at "data.photo.create.image.upload"; Upload value invalid.
已尝试方案
- 找到过一个解决方案,但已过时
- 尝试使用apollo-upload-client,但不清楚如何与Keystone适配
解决方案
1. 配置Apollo Client支持文件上传
替换默认的Apollo链接,使用apollo-upload-client提供的createUploadLink替代HttpLink,在Apollo Client初始化文件中修改:
import { ApolloClient, InMemoryCache } from '@apollo/client'; import { createUploadLink } from 'apollo-upload-client'; const client = new ApolloClient({ link: createUploadLink({ uri: '你的Keystone GraphQL端点', // 例如 http://localhost:3000/api/graphql }), cache: new InMemoryCache(), }); export default client;
2. 修正文件输入的处理逻辑
自定义useForm钩子目前将image存为字符串,无法正确获取文件对象,修改钩子的handleChange方法:
// 自定义useForm钩子的handleChange修改部分 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, files } = e.target; const inputValue = files ? files[0] : e.target.value; setInputs(prev => ({ ...prev, [name]: inputValue })); };
如果无法修改useForm,可以在组件内单独处理文件输入:
// 在ProductCreate组件中添加状态并替换input的onChange const [selectedImage, setSelectedImage] = useState<File | null>(null); // ... <input required type="file" id="image" name="image" onChange={(e) => setSelectedImage(e.target.files?.[0] || null)} />
3. 调整Mutation变量传递逻辑
不要在useMutation初始化时传入变量,改为在handleSubmit中动态传递,确保文件对象已正确获取:
// 移除useMutation中的variables参数 const [createProduct, {loading, error, data}] = useMutation(CREATE_PRODUCT_MUTATION); async function handleSubmit(e: any) { e.preventDefault(); if (!selectedImage) return; const res = await createProduct({ variables: { data: { photo: { create: { altText: `${inputs.name} featured image`, image: { upload: selectedImage } } }, name: inputs.name, price: inputs.price, description: inputs.description } } }); console.log(res); }
4. 确认Keystone的存储配置
确保Keystone中图片字段使用file类型并配置正确的本地存储:
// Keystone schema示例 import { file, list, text, integer } from '@keystone-6/core/fields'; export const lists = { Photo: list({ fields: { altText: text(), image: file({ storage: 'local_images' }) } }), Product: list({ fields: { name: text(), price: integer(), description: text(), photo: relationship({ ref: 'Photo', many: false }) } }) }; // 本地存储配置 export const storage = { local_images: { kind: 'local', type: 'file', generateUrl: path => `http://localhost:3000/images${path}`, serverRoute: { path: '/images' }, storagePath: 'public/images' } };
内容的提问来源于stack exchange,提问作者William Chorski
相关产品推荐
相关产品推荐

