diff --git a/README.md b/README.md index af8d28b..b1ee1a0 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ And then you can configure the access. - **[Getting Started Guide](./docs/getting-started.md)** - Quick setup and backend development - **[Frontend Integration Guide](./docs/frontend-integration.md)** - Build React/Vue apps with TypeScript +- **[Getting Started Guide](docs\zh-CN\getting-started.md)** - Quick setup and backend development +- **[Frontend Integration Guide](docs\zh-CN\frontend-integration.md)** - Build React/Vue apps with TypeScript ### Build Image diff --git a/docs/zh-CN/frontend-integration.md b/docs/zh-CN/frontend-integration.md new file mode 100644 index 0000000..e975217 --- /dev/null +++ b/docs/zh-CN/frontend-integration.md @@ -0,0 +1,745 @@ +# 前端集成指南 + +本指南将帮助你构建一个现代化的前端应用程序(React 或 Vue)并使用 TypeScript 连接到 WebLedger 后端。 + +## 目录 + +- [前置要求](#前置要求) +- [React + TypeScript 快速开始](#react--typescript-快速开始) +- [Vue + TypeScript 快速开始](#vue--typescript-快速开始) +- [认证设置](#认证设置) +- [API 客户端实现](#api-客户端实现) +- [使用示例](#使用示例) + +## 前置要求 + +- Node.js 18+ 和 npm/yarn/pnpm +- WebLedger 后端正在运行(参见 [开始使用](./getting-started.md)) +- 访问凭证(access 和 secret) + +## React + TypeScript 快速开始 + +### 1. 使用 Vite 创建 React 应用 + +```bash +npm create vite@latest my-ledger-app -- --template react-ts +cd my-ledger-app +npm install +``` + +### 2. 安装依赖 + +```bash +npm install axios +# 可选:用于状态管理 +npm install zustand +# 可选:用于路由 +npm install react-router-dom +``` + +### 3. 创建 API 客户端 + +创建 `src/api/client.ts`: + +```typescript +import axios, { AxiosInstance } from 'axios'; + +const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:5143'; +const ACCESS_KEY = import.meta.env.VITE_WL_ACCESS || 'root'; +const SECRET_KEY = import.meta.env.VITE_WL_SECRET || ''; + +export const apiClient: AxiosInstance = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + 'wl-access': ACCESS_KEY, + 'wl-secret': SECRET_KEY, + }, +}); + +// 请求拦截器用于日志记录 +apiClient.interceptors.request.use( + (config) => { + console.log('请求:', config.method?.toUpperCase(), config.url); + return config; + }, + (error) => { + return Promise.reject(error); + } +); + +// 响应拦截器用于错误处理 +apiClient.interceptors.response.use( + (response) => response, + (error) => { + console.error('API 错误:', error.response?.data || error.message); + return Promise.reject(error); + } +); +``` + +### 4. 创建类型定义 + +创建 `src/types/ledger.ts`: + +```typescript +export interface Entry { + id?: string; + type: string; + category: string; + amount: number; + givenTime: string; + description?: string; +} + +export interface Category { + name: string; + parent?: string; + description?: string; +} + +export interface SelectOption { + startTime?: string; + endTime?: string; + categories?: string[]; + types?: string[]; + limit?: number; + offset?: number; +} + +export interface LedgerType { + name: string; + defaultCategory: string; + description?: string; +} +``` + +### 5. 创建 API 服务 + +创建 `src/api/ledgerService.ts`: + +```typescript +import { apiClient } from './client'; +import { Entry, Category, SelectOption, LedgerType } from '../types/ledger'; + +export const ledgerService = { + // 条目操作 + async createEntry(entry: Entry): Promise { + const response = await apiClient.post('/ledger/entry', entry); + return response.data; + }, + + async deleteEntry(id: string): Promise { + await apiClient.delete(`/ledger/entry?id=${id}`); + }, + + async selectEntries(option: SelectOption): Promise { + const response = await apiClient.post('/ledger/select', option); + return response.data; + }, + + // 分类操作 + async addOrUpdateCategory(category: Category): Promise { + await apiClient.put('/ledger/category', category); + }, + + async deleteCategory(categoryName: string): Promise { + await apiClient.delete(`/ledger/category?category=${categoryName}`); + }, + + async listCategories(): Promise { + const response = await apiClient.get('/ledger/categories'); + return response.data; + }, + + // 类型操作 + async addOrUpdateType(type: LedgerType): Promise { + await apiClient.put('/ledger/type', type); + }, + + async listTypes(): Promise { + const response = await apiClient.get('/ledger/types'); + return response.data; + }, +}; +``` + +### 6. 环境变量 + +创建 `.env.local`: + +```env +VITE_API_URL=http://localhost:5143 +VITE_WL_ACCESS=root +VITE_WL_SECRET=your-secret-key-here +``` + +### 7. 示例组件 + +创建 `src/components/EntryForm.tsx`: + +```typescript +import React, { useState } from 'react'; +import { ledgerService } from '../api/ledgerService'; +import { Entry } from '../types/ledger'; + +export const EntryForm: React.FC = () => { + const [formData, setFormData] = useState>({ + type: '', + category: '', + amount: 0, + givenTime: new Date().toISOString(), + description: '', + }); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + try { + const id = await ledgerService.createEntry(formData as Entry); + console.log('创建的条目:', id); + alert('条目创建成功!'); + // 重置表单 + setFormData({ + type: '', + category: '', + amount: 0, + givenTime: new Date().toISOString(), + description: '', + }); + } catch (error) { + console.error('创建条目失败:', error); + alert('创建条目失败'); + } + }; + + return ( +
+
+ + setFormData({ ...formData, type: e.target.value })} + required + /> +
+
+ + setFormData({ ...formData, category: e.target.value })} + required + /> +
+
+ + setFormData({ ...formData, amount: parseFloat(e.target.value) })} + required + /> +
+
+ + setFormData({ ...formData, givenTime: new Date(e.target.value).toISOString() })} + required + /> +
+
+ +