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

SvelteKit中如何在lib组件调用Prisma增删改函数?

解决SvelteKit弹窗组件调用Prisma的方案

核心逻辑

Prisma只能在服务端上下文(比如+page.server.js、+server.js)中运行,前端组件(lib目录下的组件属于客户端/通用组件)无法直接导入使用。必须通过服务端动作(Form Actions)或者API端点中转数据库操作,这是唯一合规的实现路径。


方式一:使用SvelteKit Form Actions(推荐)

这是SvelteKit原生推荐的表单处理方案,无需额外编写API路由,直接在页面的服务端文件中定义动作即可。

1. 在+page.server.js中定义增删改动作

import { prisma } from '$lib/prisma'; // 假设你的Prisma实例存放在lib/prisma.js
import { fail } from '@sveltejs/kit';

// 原有加载areas的逻辑保留
export async function load() {
  const areas = await prisma.area.findMany();
  return { areas };
}

// 定义Form Actions处理数据库操作
export const actions = {
  createComment: async ({ request }) => {
    const data = await request.formData();
    const author = data.get('author');
    const comment = data.get('comment');
    const areaId = data.get('areaId');

    if (!author || !comment || !areaId) {
      return fail(400, { error: '必填项不能为空' });
    }

    try {
      await prisma.comment.create({
        data: {
          author,
          comment,
          areaId: parseInt(areaId)
        }
      });
      return { success: true };
    } catch (err) {
      return fail(500, { error: '创建评论失败' });
    }
  },
  editComment: async ({ request }) => {
    const data = await request.formData();
    const id = data.get('id');
    const author = data.get('author');
    const comment = data.get('comment');

    if (!id || !author || !comment) {
      return fail(400, { error: '必填项不能为空' });
    }

    try {
      await prisma.comment.update({
        where: { id: parseInt(id) },
        data: { author, comment }
      });
      return { success: true };
    } catch (err) {
      return fail(500, { error: '编辑评论失败' });
    }
  },
  deleteComment: async ({ request }) => {
    const data = await request.formData();
    const id = data.get('id');

    if (!id) {
      return fail(400, { error: '缺少评论ID' });
    }

    try {
      await prisma.comment.delete({
        where: { id: parseInt(id) }
      });
      return { success: true };
    } catch (err) {
      return fail(500, { error: '删除评论失败' });
    }
  }
};

2. 修改弹窗组件(Modal.svelte)的提交逻辑

替换原有空实现的handleSave、handleEdit、handleDelete,改为调用上面定义的服务端动作:

<script>
  import { showModalBackdrop, modalAreaIdStore, modalCommentIdStore, refreshComments, commentToEditStore } from '$lib/stores';
  import Swal from 'sweetalert2';

  let author = '';
  let comment = '';
  let showConfirmDeleteButton = false;
  let files = { accepted: [] };

  function handleFilesSelect(event) {
    files = event.detail;
  }

  function clearForm() {
    author = '';
    comment = '';
    showConfirmDeleteButton = false;
    files = { accepted: [] };
  }

  function getComment() {
    if ($commentToEditStore) {
      author = $commentToEditStore.author;
      comment = $commentToEditStore.comment;
    }
  }

  // 通用提交函数
  async function submitAction(action, formData) {
    const response = await fetch('/?/'+action, {
      method: 'POST',
      body: formData
    });

    const result = await response.json();
    if (result.success) {
      // 重置状态并刷新数据
      $modalAreaIdStore = 0;
      $modalCommentIdStore = 0;
      $showModalBackdrop = false;
      $refreshComments = !$refreshComments;
      Swal.fire('操作成功');
    } else {
      Swal.fire(result.error);
    }
  }

  const handleSave = () => {
    if (!author || !comment) {
      Swal.fire('作者和评论内容不能为空');
      return;
    }

    const formData = new FormData();
    formData.append('author', author);
    formData.append('comment', comment);
    formData.append('areaId', $modalAreaIdStore);
    
    submitAction('createComment', formData);
  };

  const handleEdit = () => {
    if (!author || !comment) {
      Swal.fire('作者和评论内容不能为空');
      return;
    }

    const formData = new FormData();
    formData.append('id', $commentToEditStore.entry_id);
    formData.append('author', author);
    formData.append('comment', comment);
    
    submitAction('editComment', formData);
  };

  async function handleDelete(id) {
    const formData = new FormData();
    formData.append('id', id);
    
    submitAction('deleteComment', formData);
  }

  $: $commentToEditStore, getComment();
  $: $showModalBackdrop, clearForm();
</script>

<!-- 原有弹窗HTML结构无需修改,仅按钮点击逻辑已在script中更新 -->
<div
  class="{$showAddComment ? '' : 'hidden'} fixed top-1/4 left-1/2 -translate-x-1/2 -translate-y-1/2 w-1/3 border-2 rounded-lg bg-slate-100 font-bold addCommentModal"
>
  <div class="m-4">
    <div class="text-lg pb-4 border-b-2">
      <h1>{$modalAreaIdStore != 0 ? '添加安全项:' : '编辑安全项:'}</h1>
    </div>
    <div>
      <label for="author">作者</label>
      <input
        type="text"
        class="bg-slate-100 w-full rounded-lg p-2 outline outline-blue-500"
        bind:value={author}
      />
    </div>
    <div>
      <label for="comment">评论</label>
      <textarea
        name="comment"
        id="comment"
        cols="22"
        rows="5"
        class="bg-slate-100 w-full border rounded-lg focus:border-white p-2"
        bind:value={comment}
      />
    </div>
    <div>
      <Dropzone on:drop={handleFilesSelect} />
      <ol>
        {#each files.accepted as item}
          <li>{item.name}</li>
          <li>{item.size}</li>
        {/each}
      </ol>
    </div>
    <div>
      {#if $modalAreaIdStore === 0}
        <button
          class="bg-red-500 hover:bg-red-700 text-white py-2 px-4 rounded"
          on:click={() => (showConfirmDeleteButton = true)}
        >
          删除
        </button>
        {#if showConfirmDeleteButton}
          <button class="" on:click={() => handleDelete($commentToEditStore.entry_id)}>确认删除</button>
        {/if}
      {/if}

      <div class="float-right">
        <button
          class="m-2 bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded"
          on:click={$modalCommentIdStore === 0 ? handleSave : handleEdit}
        >
          保存
        </button>
        <button
          class="m-2 bg-slate-300 hover:bg-slate-500 font-bold py-2 px-4 rounded"
          on:click={() => {
            $modalAreaIdStore = 0;
            $modalCommentIdStore = 0;
            $showModalBackdrop = false;
          }}
        >
          取消
        </button>
      </div>
    </div>
  </div>
</div>

方式二:创建API端点(+server.js)

如果偏好RESTful API模式,可以单独创建API路由处理数据库操作:

1. 创建routes/api/comments/+server.js

import { prisma } from '$lib/prisma';
import { json, error } from '@sveltejs/kit';

export async function POST({ request }) {
  const { action, data } = await request.json();

  try {
    switch(action) {
      case 'create':
        await prisma.comment.create({ data });
        return json({ success: true });
      case 'edit':
        await prisma.comment.update({
          where: { id: data.id },
          data: { author: data.author, comment: data.comment }
        });
        return json({ success: true });
      case 'delete':
        await prisma.comment.delete({ where: { id: data.id } });
        return json({ success: true });
      default:
        throw error(400, '无效的动作类型');
    }
  } catch (err) {
    throw error(500, err.message);
  }
}

2. 修改弹窗组件的提交逻辑

将submitAction改为调用API:

async function submitAction(action, payload) {
  try {
    const response = await fetch('/api/comments', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ action, data: payload })
    });

    if (response.ok) {
      $modalAreaIdStore = 0;
      $modalCommentIdStore = 0;
      $showModalBackdrop = false;
      $refreshComments = !$refreshComments;
      Swal.fire('操作成功');
    } else {
      const errData = await response.json();
      Swal.fire(errData.message || '操作失败');
    }
  } catch (err) {
    Swal.fire('网络错误,请稍后重试');
  }
}

// 对应调整handleSave、handleEdit、handleDelete的参数
const handleSave = () => {
  if (!author || !comment) {
    Swal.fire('作者和评论不能为空');
    return;
  }
  submitAction('create', { author, comment, areaId: $modalAreaIdStore });
};

const handleEdit = () => {
  if (!author || !comment) {
    Swal.fire('作者和评论不能为空');
    return;
  }
  submitAction('edit', { id: $commentToEditStore.entry_id, author, comment });
};

async function handleDelete(id) {
  submitAction('delete', { id });
}

额外注意事项

  • Prisma实例必须在服务端安全初始化,绝对不能在客户端代码中暴露数据库连接信息
  • 文件上传逻辑需在服务端动作中处理,前端仅负责传递文件数据
  • 可通过refreshComments store触发页面数据重新加载,确保操作后界面同步最新状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 18:07:22