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

相关推荐
2301_796982145 分钟前
下面html程序中有什么错误?怎样修改?
前端·javascript·html
技术钱36 分钟前
前端项目打包构建优化
前端
By爱分享41 分钟前
vue使用keep-alive缓存页面状态问题
前端·javascript·vue.js
韩沛伦1 小时前
React为什么设计Hooks?Hooks解决了什么问题?
前端
Wiktok1 小时前
CSS实现当鼠标悬停在一个元素上时,另一个元素的样式发生变化的效果
前端·css
battlestar2 小时前
Siemens Smart 200 PLC 通讯(基于python-)
前端·网络·python
孔子梦周公2 小时前
tailwind v3 升级 v4
前端
Tonychen2 小时前
【React 源码阅读】useCallback
前端·react.js
我不是迈巴赫2 小时前
项目亮点万金油:自定义SSR水合保护hooks
前端·javascript·react.js
不想上班只想要钱2 小时前
vue el-table 设置selection选中状态
前端·javascript·vue.js