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

Prisma可选关联下品牌/颜色/尺寸删除限制失效问题

问题描述

我使用Prisma、NextJS和Supabase搭建电商管理后台,希望让品牌、颜色、尺寸等产品筛选项在创建产品时设为可选。但设置为可选后,校验这些筛选项是否被产品关联的删除限制功能失效了。例如分类为必填项,用户删除分类前必须先删除所有关联该分类的产品;我需要让品牌、颜色、尺寸也保持同样的规则——关联产品未删除前无法删除这些筛选项。


现有代码

schema.prisma

model Product {
 id           String          @id @default(uuid())
 storeId      String
 store        Store           @relation("StoreToProduct", fields: [storeId], references: [id])
 categoryId   String 
 category     Category        @relation("CategoryToProduct", fields: [categoryId], references: [id])
 name         String
 price        Decimal
 isFeatured   Boolean         @default(false)
 isArchived   Boolean         @default(false)
 sizeId       String?       
 size         Size?           @relation(fields: [sizeId], references: [id])
 colorId      String?       
 color        Color?          @relation(fields: [colorId], references: [id])
 brandId      String?       
 brand        Brand?          @relation(fields: [brandId], references: [id])
 images       Image[]
 saleItems    SaleItem[]
 createdAt    DateTime        @default(now())
 updatedAt    DateTime        @updatedAt

 @@index([storeId])
 @@index([categoryId])
 @@index([brandId])
 @@index([sizeId])
 @@index([colorId])
}

product.form.tsx

"use client";

import { UseOrigin } from "@/hooks/use-origin";
import { zodResolver } from "@hookform/resolvers/zod";
import { Brand, Category, Color, Image, Product, Size } from "@prisma/client";
import axios from "axios";
import { useParams, useRouter } from "next/navigation";
import { useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import * as z from "zod";

const formSchema = z.object({
 name: z.string().min(1),
 images: z.object({ url: z.string() }).array(),
 price: z.coerce.number().min(1),
 categoryId: z.string().min(1),
 colorId: z.string().min(1).optional(),
 sizeId: z.string().min(1).optional(),
 brandId: z.string().min(1).optional(),
 isFeatured: z.boolean().default(false).optional(),
 isArchived: z.boolean().default(false).optional(),
});

interface ProductFormProps {
 initialData:
 | (Product & {
    images: Image[];
  })
 | null;
categories: Category[];
colors?: Color[];
sizes?: Size[];
brands?: Brand[];
}

type ProductFormValues = z.infer<typeof formSchema>;

export const ProductForm: React.FC<ProductFormProps> = ({
 initialData,
 categories,
 brands,
 colors,
 sizes,
}) => {
 const params = useParams();
 const router = useRouter();
 const origin = UseOrigin();
 const [open, setOpen] = useState(false);
 const [loading, setLoading] = useState(false);

const form = useForm<ProductFormValues>({
 resolver: zodResolver(formSchema),
 defaultValues: initialData
   ? {
      ...initialData,
      price: parseFloat(String(initialData?.price)),
      brandId: initialData.brandId || undefined,
      colorId: initialData.colorId || undefined,
      sizeId: initialData.sizeId || undefined,
    }
   : {
      name: "",
      images: [],
      price: 0,
      categoryId: "",
      colorId: undefined,
      sizeId: undefined,
      brandId: undefined,
      isFeatured: false,
      isArchived: false,
    },
 });

删除逻辑(color-form.tsx / cell-actions.tsx)

// color-form.tsx 中的删除函数
const onDelete = async () => {
 try {
  setLoading(true);
  await axios.delete(`/api/${params.storeId}/colors/${params.colorId}`);
  router.refresh();
  router.push(`/${params.storeId}/colors`);
  toast.success("Color deleted.");
} catch (error) {
  toast.error("Make sure you removed all products using this color first");
} finally {
  setLoading(false);
  setOpen(false);
}
};

// cell-actions.tsx 中的删除函数
const onDelete = async () => {
  try {
   setLoading(true);
   await axios.delete(`/api/${params.storeId}/colors/${data.id}`);
   router.refresh();
   toast.success("Color deleted.");
  } catch (error) {
   toast.error("Make sure you removed all products using this color first");
  } finally {
   setLoading(false);
   setOpen(false);
 }
};

解决方案

1. 完善Prisma模型的反向关系

确保Size、Color、Brand模型中配置与Product的反向关联,显式设置删除约束为RESTRICT(默认即为该值,显式声明更清晰):

// Size模型示例,Color、Brand模型同理
model Size {
  id        String   @id @default(uuid())
  name      String
  storeId   String
  store     Store    @relation(fields: [storeId], references: [id])
  products  Product[] // 反向关联产品
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([storeId])
}

2. 后端API添加关联检查

在删除颜色、品牌、尺寸的API路由中,先检查是否有产品关联该选项,有则返回错误,无则执行删除。以颜色为例:

// app/api/[storeId]/colors/[colorId]/route.ts
import { prisma } from '@/lib/prisma';
import { NextResponse } from 'next/server';

export async function DELETE(
  req: Request,
  { params }: { params: { storeId: string; colorId: string } }
) {
  try {
    const { storeId, colorId } = params;

    // 检查是否有未归档的关联产品
    const relatedProductsCount = await prisma.product.count({
      where: {
        storeId,
        colorId,
        isArchived: false,
      },
    });

    if (relatedProductsCount > 0) {
      return new NextResponse('该颜色仍有关联产品,无法删除', { status: 400 });
    }

    // 执行删除
    const deletedColor = await prisma.color.delete({
      where: {
        id: colorId,
        storeId,
      },
    });

    return NextResponse.json(deletedColor);
  } catch (error) {
    return new NextResponse('删除失败,请确保已移除所有关联产品', { status: 500 });
  }
}

品牌和尺寸的API路由复制上述逻辑,替换对应的模型名称即可。

3. 优化前端错误提示

根据后端返回的错误信息动态提示,提升用户体验:

// color-form.tsx 中的onDelete修改
const onDelete = async () => {
  try {
    setLoading(true);
    await axios.delete(`/api/${params.storeId}/colors/${params.colorId}`);
    router.refresh();
    router.push(`/${params.storeId}/colors`);
    toast.success('颜色已删除');
  } catch (error: any) {
    const errorMessage = error.response?.data || '请先移除所有使用该颜色的产品';
    toast.error(errorMessage);
  } finally {
    setLoading(false);
    setOpen(false);
  }
};

// cell-actions.tsx 中的onDelete同理修改

4. 确认前端表单可选配置

当前product.form.tsx中的Zod Schema已将colorId、sizeId、brandId设为optional(),创建产品时可正常跳过这些选项,无需修改。


内容的提问来源于stack exchange,提问作者Jean Mako

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 16:05:53