You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 05:35:07