基于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 是否更新。

相关推荐
Jimmy9 分钟前
TypeScript 泛型:2025 年终极指南
前端·javascript·typescript
来来走走15 分钟前
Flutter dart运算符
android·前端·flutter
moddy20 分钟前
新人怎么去做低代码,并且去使用?
前端
风清云淡_A20 分钟前
【Flutter3.8x】flutter从入门到实战基础教程(五):Material Icons图标的使用
前端·flutter
安心不心安25 分钟前
React ahooks——副作用类hooks之useThrottleEffect
前端·react.js·前端框架
jstart千语27 分钟前
【vue】创建响应式数据ref和reactive的区别
前端·javascript·vue.js
肖师叔爱抽风34 分钟前
使用nvm use切换版本时出现虚假成功信息,使用nvm ls,实际显示没有切换成功,解决方法
前端
猩猩程序员36 分钟前
Rust为什么需要Borrow Trait
前端
BUG收容所所长36 分钟前
Zustand状态管理如何驱动低代码平台的数据流?
前端·javascript·设计
司宸40 分钟前
学习笔记十四 —— 嵌套JSON树结构 实现模糊匹配返回搜索路径
前端