基于Next api与本地文件存储前端配置项

上一篇文章中我们使用了浏览器IndexedDB 存储了 cardConfig 的客户端数据,在实际生产更多的场景需要根据用户获取配置数据,因此更合适的是将数据放在服务端。 因此我们做以下变更:

  1. 搭建服务端:使用 Next.js 的 API 路由。
  2. 选择存储方式:服务端选择文件存储数据。
  3. 修改前端代码:通过 API 请求从服务端获取和更新数据,而不是直接操作 IndexedDB。
  4. 部署调整:确保服务端在生产环境中正确运行。

以下是一个基于 Next.js API 路由和文件系统存储的实现方案,简单且无需额外依赖数据库。


1. 创建服务端 API 路由

Next.js 支持在 pages/api 目录下创建 API 路由。我们将创建一个 API 来处理 cardConfig 的获取和更新。

创建 API 文件

pages/api/cardConfig.ts 中添加以下代码:

typescript 复制代码
// pages/api/cardConfig.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import fs from 'fs/promises';
import path from 'path';
import { CardItem } from '../../types/card';
import { defaultCardConfig } from '../../config/cardConfig';

// 数据存储路径
const dataFilePath = path.join(process.cwd(), 'data', 'cardConfig.json');

// 初始化数据文件(如果不存在)
const initData = async () => {
  try {
    await fs.access(dataFilePath);
  } catch {
    await fs.mkdir(path.dirname(dataFilePath), { recursive: true });
    await fs.writeFile(dataFilePath, JSON.stringify(defaultCardConfig, null, 2));
  }
};

// 获取配置
const getConfig = async (): Promise<CardItem[]> => {
  await initData();
  const data = await fs.readFile(dataFilePath, 'utf-8');
  return JSON.parse(data);
};

// 更新配置
const updateConfig = async (config: CardItem[]) => {
  await fs.writeFile(dataFilePath, JSON.stringify(config, null, 2));
};

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    if (req.method === 'GET') {
      const config = await getConfig();
      res.status(200).json(config);
    } else if (req.method === 'POST') {
      const newConfig: CardItem[] = req.body;
      await updateConfig(newConfig);
      res.status(200).json({ message: 'Config updated successfully' });
    } else {
      res.status(405).json({ message: 'Method not allowed' });
    }
  } catch (error) {
    res.status(500).json({ message: 'Server error', error: (error as Error).message });
  }
}
  • 说明
    • GET /api/cardConfig:获取当前配置。
    • POST /api/cardConfig:更新配置。
    • 数据存储在项目根目录下的 data/cardConfig.json 文件中。
    • 如果文件不存在,首次请求时会用 defaultCardConfig 初始化。

创建初始数据目录

确保项目根目录下有 data 文件夹,或者让代码自动创建。如果你手动创建,可以添加一个空的 data/cardConfig.json 文件,或者让程序初始化。


2. 修改前端代码

pages/index.tsx 从 IndexedDB 操作改为通过 API 获取和更新数据。

typescript 复制代码
// pages/index.tsx
import { useState, useEffect } from "react";
import {
  ConfigProvider,
  Button,
  Modal,
  Input,
  Space,
} from "@arco-design/web-react";
import CardList from "../components/CardList";
import { CardItem } from "../types/card";

export default function Home() {
  const [cards, setCards] = useState<CardItem[]>([]);
  const [isModalVisible, setIsModalVisible] = useState(false);
  const [editingCard, setEditingCard] = useState<CardItem | null>(null);

  // 获取数据
  const fetchConfig = async () => {
    const res = await fetch("/api/cardConfig");
    const data = await res.json();
    setCards(data);
  };

  // 更新数据
  const updateConfig = async (newConfig: CardItem[]) => {
    const res = await fetch("/api/cardConfig", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(newConfig),
    });
    if (res.ok) {
      setCards(newConfig);
    } else {
      console.error("Failed to update config");
    }
  };

  // 初始化加载数据
  useEffect(() => {
    fetchConfig();
  }, []);

  // 编辑卡片
  const handleEdit = (card: CardItem) => {
    setEditingCard({ ...card });
    setIsModalVisible(true);
  };

  // 保存编辑
  const handleSave = async () => {
    if (editingCard) {
      const updatedCards = cards.map((card) =>
        card.id === editingCard.id ? editingCard : card
      );
      await updateConfig(updatedCards);
      setIsModalVisible(false);
      setEditingCard(null);
    }
  };

  return (
    <ConfigProvider>
      <div style={{ padding: "20px" }}>
        <h1 style={{ marginBottom: "20px" }}>页面目录</h1>
        <CardList items={cards} onEdit={handleEdit} />
        <Modal
          title="编辑卡片"
          visible={isModalVisible}
          onOk={handleSave}
          onCancel={() => setIsModalVisible(false)}
          style={{ width: 600 }}
        >
          {editingCard && (
            <Space direction="vertical" size="large" style={{ width: "100%" }}>
              <Input
                prefix="标题"
                value={editingCard.title}
                onChange={(value) =>
                  setEditingCard({ ...editingCard, title: value })
                }
              />
              <Input
                prefix="描述"
                value={editingCard.description}
                onChange={(value) =>
                  setEditingCard({ ...editingCard, description: value })
                }
              />
              <Input
                prefix="主URL"
                value={editingCard.url}
                onChange={(value) =>
                  setEditingCard({ ...editingCard, url: value })
                }
              />
            </Space>
          )}
        </Modal>
      </div>
    </ConfigProvider>
  );
}
  • 说明
    • fetchConfig:从 /api/cardConfig 获取数据。
    • updateConfig:通过 POST 请求更新服务端数据。

3. 测试本地运行

  1. 运行项目:

    bash 复制代码
    npm run dev
  2. 访问 http://localhost:3000,检查卡片数据是否正确加载。

  3. 编辑一个卡片,保存后检查 data/cardConfig.json 是否更新。

相关推荐
weixin_382395232 小时前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__2 小时前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.3 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~4 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华5 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子7 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅7 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni7 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学9 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端