上一篇文章中我们使用了浏览器IndexedDB 存储了 cardConfig
的客户端数据,在实际生产更多的场景需要根据用户获取配置数据,因此更合适的是将数据放在服务端。 因此我们做以下变更:
- 搭建服务端:使用 Next.js 的 API 路由。
- 选择存储方式:服务端选择文件存储数据。
- 修改前端代码:通过 API 请求从服务端获取和更新数据,而不是直接操作 IndexedDB。
- 部署调整:确保服务端在生产环境中正确运行。
以下是一个基于 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. 测试本地运行
-
运行项目:
bashnpm run dev
-
访问
http://localhost:3000
,检查卡片数据是否正确加载。 -
编辑一个卡片,保存后检查
data/cardConfig.json
是否更新。