feat: init
This commit is contained in:
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# prod
|
||||||
|
dist/
|
||||||
|
|
||||||
|
# dev
|
||||||
|
.yarn/
|
||||||
|
!.yarn/releases
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/usage.statistics.xml
|
||||||
|
.idea/shelf
|
||||||
|
|
||||||
|
# deps
|
||||||
|
node_modules/
|
||||||
|
.wrangler
|
||||||
|
|
||||||
|
# env
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
.dev.vars
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Tencent Cloud EdgeOne
|
||||||
|
.env
|
||||||
|
.edgeone
|
||||||
|
.edgeone/*
|
||||||
|
.tef_dist/*
|
||||||
|
|
||||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
||||||
307
README.md
Normal file
307
README.md
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
# EdgeOne Pages Hono Application
|
||||||
|
|
||||||
|
This is a modern Web application built on the [Hono](https://hono.dev/) framework, deployed on the EdgeOne Pages platform.
|
||||||
|
|
||||||
|
Live demo: https://hono-template.edgeone.app
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
[](https://edgeone.ai/pages/new?from=github&template=hono)
|
||||||
|
|
||||||
|
## 🚀 Project Features
|
||||||
|
|
||||||
|
- **Modular Route Design** - Clear route organization structure
|
||||||
|
- **Server-Side Rendering** - Page rendering using JSX and HTML templates
|
||||||
|
- **File Upload** - File upload functionality support
|
||||||
|
- **Book Management** - Example CRUD operations
|
||||||
|
- **Error Handling** - Beautiful 404 and 500 error pages
|
||||||
|
- **TypeScript Support** - Complete type definitions
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
functions/
|
||||||
|
├── index.tsx # Main entry file
|
||||||
|
├── [[default]].ts # EdgeOne Functions default route
|
||||||
|
├── env.ts # Environment type definitions
|
||||||
|
├── components/ # Components directory
|
||||||
|
│ └── Layout.tsx # Page layout component
|
||||||
|
└── routers/ # Route modules
|
||||||
|
├── index.ts # Unified route exports
|
||||||
|
├── book.tsx # Book related routes
|
||||||
|
├── ssr.tsx # Server-side rendering routes
|
||||||
|
└── upload.ts # File upload routes
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛣️ Route Details
|
||||||
|
|
||||||
|
### Static Routes
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|------|------|------|
|
||||||
|
| `/` | GET | Static home page, serves `index.html` from public directory |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `https://hono.edgeone.app/` - Static home page
|
||||||
|
|
||||||
|
### SSR Routes (`/ssr`)
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|------|------|------|
|
||||||
|
| `/ssr/:name` | GET | Dynamic SSR page, displays personalized welcome message |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `https://hono.edgeone.app/ssr/john` - Shows "Hello john!" page
|
||||||
|
|
||||||
|
### Book Management Routes (`/book`)
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|------|------|------|
|
||||||
|
| `/book` | GET | Get all books list page |
|
||||||
|
| `/book/:id` | GET | Get specific book details page |
|
||||||
|
| `/book` | POST | Create new book (API endpoint) |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
- `https://hono.edgeone.app/book` - Book list
|
||||||
|
- `https://hono.edgeone.app/book/1` - Details of the first book
|
||||||
|
|
||||||
|
**Create Book API Request Example:**
|
||||||
|
```bash
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "New Book Title",
|
||||||
|
"author": "Author Name"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Supported Features:**
|
||||||
|
- CORS cross-origin support
|
||||||
|
|
||||||
|
### File Upload Routes (`/upload`)
|
||||||
|
|
||||||
|
| Path | Method | Description |
|
||||||
|
|------|------|------|
|
||||||
|
| `/upload` | POST | File upload endpoint |
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@example.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Detailed API Documentation
|
||||||
|
|
||||||
|
### Basic Information
|
||||||
|
|
||||||
|
- **Base URL**: `https://hono.edgeone.app`
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
- **Encoding**: UTF-8
|
||||||
|
|
||||||
|
### API Details
|
||||||
|
|
||||||
|
#### 1. File Upload
|
||||||
|
|
||||||
|
**Endpoint**: `POST /upload`
|
||||||
|
|
||||||
|
**Description**: Upload files to server
|
||||||
|
|
||||||
|
**Request Format**: `multipart/form-data`
|
||||||
|
|
||||||
|
**Request Parameters**:
|
||||||
|
- `file` (required): File to upload
|
||||||
|
|
||||||
|
**curl Request Examples**:
|
||||||
|
```bash
|
||||||
|
# Upload text file
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@/path/to/your/file.txt"
|
||||||
|
|
||||||
|
# Upload image file
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@/path/to/image.jpg"
|
||||||
|
|
||||||
|
# Upload with custom filename
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@document.pdf;filename=my-document.pdf"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "File uploaded successfully",
|
||||||
|
"fileName": "file.txt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Error Response**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "No file provided"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. Create Book
|
||||||
|
|
||||||
|
**Endpoint**: `POST /book`
|
||||||
|
|
||||||
|
**Description**: Create new book record
|
||||||
|
|
||||||
|
**Request Parameters**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Book Title",
|
||||||
|
"author": "Author Name"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameter Description**:
|
||||||
|
- `title` (optional): Book title, defaults to "Untitled"
|
||||||
|
- `author` (optional): Author name, defaults to "Unknown"
|
||||||
|
|
||||||
|
**curl Request Examples**:
|
||||||
|
```bash
|
||||||
|
# Create book with complete information
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "Dream of the Red Chamber",
|
||||||
|
"author": "Cao Xueqin"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Create book with only title
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "New Book Title"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Create empty book (using defaults)
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response Example**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Book created successfully",
|
||||||
|
"book": {
|
||||||
|
"id": "abc123def",
|
||||||
|
"title": "Book Title",
|
||||||
|
"author": "Author Name",
|
||||||
|
"createdAt": "2023-12-01T10:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. Get Book Information
|
||||||
|
|
||||||
|
**curl Request Examples**:
|
||||||
|
```bash
|
||||||
|
# Get all books list
|
||||||
|
curl -X GET https://hono.edgeone.app/book
|
||||||
|
|
||||||
|
# Get specific book details
|
||||||
|
curl -X GET https://hono.edgeone.app/book/1
|
||||||
|
|
||||||
|
# Get personal page
|
||||||
|
curl -X GET https://hono.edgeone.app/john
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Code Description
|
||||||
|
|
||||||
|
| Error Code | HTTP Status Code | Description |
|
||||||
|
|-----------|-------------|------|
|
||||||
|
| `VALIDATION_ERROR` | 400 | Request parameter validation failed |
|
||||||
|
| `FILE_UPLOAD_ERROR` | 400 | File upload failed |
|
||||||
|
| `NOT_FOUND` | 404 | Resource not found |
|
||||||
|
| `INTERNAL_ERROR` | 500 | Internal server error |
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
- All API endpoints currently have no rate limiting
|
||||||
|
- Client-side request frequency control is recommended
|
||||||
|
|
||||||
|
### CORS Support
|
||||||
|
|
||||||
|
All API endpoints support cross-origin access, response headers include:
|
||||||
|
- `Access-Control-Allow-Origin: *`
|
||||||
|
- `Access-Control-Allow-Methods: POST, GET, OPTIONS`
|
||||||
|
- `Access-Control-Allow-Headers: Content-Type, Authorization`
|
||||||
|
|
||||||
|
## 🔧 Development
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Start development server
|
||||||
|
edgeone pages dev
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 🌐 Environment Variables
|
||||||
|
|
||||||
|
The project uses the following environment variables and global objects:
|
||||||
|
|
||||||
|
- `my_kv` - KV storage instance for data persistence
|
||||||
|
|
||||||
|
## 🛡️ Security Features
|
||||||
|
|
||||||
|
### IP Restriction (Optional)
|
||||||
|
|
||||||
|
The project includes IP restriction middleware configuration (commented by default), which can limit access sources:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.use('*', ipRestriction(/* configuration */));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 API Response Format
|
||||||
|
|
||||||
|
### Success Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "Operation successful",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Error Response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "ERROR_CODE",
|
||||||
|
"message": "Error description"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 UI Design
|
||||||
|
|
||||||
|
The project adopts modern UI design:
|
||||||
|
- Responsive layout
|
||||||
|
- System font stack
|
||||||
|
- Card-style design
|
||||||
|
- Unified color theme
|
||||||
|
- Elegant error pages
|
||||||
|
|
||||||
|
## 📦 Dependencies
|
||||||
|
|
||||||
|
- **hono** - Web framework
|
||||||
|
- **@edgeone/ef-types** - EdgeOne Functions type definitions
|
||||||
|
- **edgeone** - EdgeOne CLI tool
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Welcome to submit Issues and Pull Requests to improve this project.
|
||||||
|
|
||||||
|
## 📄 License
|
||||||
|
|
||||||
|
MIT License
|
||||||
306
README_zh-CN.md
Normal file
306
README_zh-CN.md
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
# EdgeOne Pages Hono 应用程序
|
||||||
|
|
||||||
|
这是一个基于 [Hono](https://hono.dev/) 框架构建的现代 Web 应用程序,部署在 EdgeOne Pages 平台上。
|
||||||
|
|
||||||
|
在线演示:https://hono.edgeone.site
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
[](https://console.cloud.tencent.com/edgeone/pages/new?template=hono)
|
||||||
|
|
||||||
|
## 🚀 项目特性
|
||||||
|
|
||||||
|
- **模块化路由设计** - 清晰的路由组织结构
|
||||||
|
- **服务端渲染** - 使用 JSX 和 HTML 模板进行页面渲染
|
||||||
|
- **文件上传** - 文件上传功能支持
|
||||||
|
- **图书管理** - 示例 CRUD 操作
|
||||||
|
- **错误处理** - 精美的 404 和 500 错误页面
|
||||||
|
- **TypeScript 支持** - 完整的类型定义
|
||||||
|
|
||||||
|
## 📁 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
functions/
|
||||||
|
├── index.tsx # 主入口文件
|
||||||
|
├── [[default]].ts # EdgeOne Functions 默认路由
|
||||||
|
├── env.ts # 环境类型定义
|
||||||
|
├── components/ # 组件目录
|
||||||
|
│ └── Layout.tsx # 页面布局组件
|
||||||
|
└── routers/ # 路由模块
|
||||||
|
├── index.ts # 统一路由导出
|
||||||
|
├── book.tsx # 图书相关路由
|
||||||
|
├── ssr.tsx # 服务端渲染路由
|
||||||
|
└── upload.ts # 文件上传路由
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛣️ 路由详情
|
||||||
|
|
||||||
|
### 静态路由
|
||||||
|
|
||||||
|
| 路径 | 方法 | 描述 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/` | GET | 静态首页,从 public 目录提供 `index.html` |
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
- `https://hono.edgeone.app/` - 静态首页
|
||||||
|
|
||||||
|
### SSR 路由 (`/ssr`)
|
||||||
|
|
||||||
|
| 路径 | 方法 | 描述 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/ssr/:name` | GET | 动态 SSR 页面,显示个性化欢迎消息 |
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
- `https://hono.edgeone.app/ssr/john` - 显示 "Hello john!" 页面
|
||||||
|
|
||||||
|
### 图书管理路由 (`/book`)
|
||||||
|
|
||||||
|
| 路径 | 方法 | 描述 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/book` | GET | 获取所有图书列表页面 |
|
||||||
|
| `/book/:id` | GET | 获取特定图书详情页面 |
|
||||||
|
| `/book` | POST | 创建新图书(API 端点) |
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
- `https://hono.edgeone.app/book` - 图书列表
|
||||||
|
- `https://hono.edgeone.app/book/1` - 第一本书的详情
|
||||||
|
|
||||||
|
**创建图书 API 请求示例:**
|
||||||
|
```bash
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "新书标题",
|
||||||
|
"author": "作者姓名"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**支持的功能:**
|
||||||
|
- CORS 跨域支持
|
||||||
|
|
||||||
|
### 文件上传路由 (`/upload`)
|
||||||
|
|
||||||
|
| 路径 | 方法 | 描述 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/upload` | POST | 文件上传端点 |
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```bash
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@example.txt"
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 详细 API 文档
|
||||||
|
|
||||||
|
### 基本信息
|
||||||
|
|
||||||
|
- **Base URL**: `https://hono.edgeone.app`
|
||||||
|
- **Content-Type**: `application/json`
|
||||||
|
- **编码**: UTF-8
|
||||||
|
|
||||||
|
### API 详情
|
||||||
|
|
||||||
|
#### 1. 文件上传
|
||||||
|
|
||||||
|
**端点**: `POST /upload`
|
||||||
|
|
||||||
|
**描述**: 上传文件到服务器
|
||||||
|
|
||||||
|
**请求格式**: `multipart/form-data`
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
- `file` (必需): 要上传的文件
|
||||||
|
|
||||||
|
**curl 请求示例**:
|
||||||
|
```bash
|
||||||
|
# 上传文本文件
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@/path/to/your/file.txt"
|
||||||
|
|
||||||
|
# 上传图片文件
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@/path/to/image.jpg"
|
||||||
|
|
||||||
|
# 上传并自定义文件名
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F "file=@document.pdf;filename=my-document.pdf"
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "文件上传成功",
|
||||||
|
"fileName": "file.txt"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**错误响应**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"message": "未提供文件"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 创建图书
|
||||||
|
|
||||||
|
**端点**: `POST /book`
|
||||||
|
|
||||||
|
**描述**: 创建新的图书记录
|
||||||
|
|
||||||
|
**请求参数**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "图书标题",
|
||||||
|
"author": "作者姓名"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数说明**:
|
||||||
|
- `title` (可选): 图书标题,默认为 "Untitled"
|
||||||
|
- `author` (可选): 作者姓名,默认为 "Unknown"
|
||||||
|
|
||||||
|
**curl 请求示例**:
|
||||||
|
```bash
|
||||||
|
# 创建包含完整信息的图书
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "红楼梦",
|
||||||
|
"author": "曹雪芹"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 只创建标题的图书
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"title": "新书标题"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# 创建空图书(使用默认值)
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "图书创建成功",
|
||||||
|
"book": {
|
||||||
|
"id": "abc123def",
|
||||||
|
"title": "图书标题",
|
||||||
|
"author": "作者姓名",
|
||||||
|
"createdAt": "2023-12-01T10:00:00.000Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 获取图书信息
|
||||||
|
|
||||||
|
**curl 请求示例**:
|
||||||
|
```bash
|
||||||
|
# 获取所有图书列表
|
||||||
|
curl -X GET https://hono.edgeone.app/book
|
||||||
|
|
||||||
|
# 获取特定图书详情
|
||||||
|
curl -X GET https://hono.edgeone.app/book/1
|
||||||
|
|
||||||
|
# 获取个人页面
|
||||||
|
curl -X GET https://hono.edgeone.app/john
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误码说明
|
||||||
|
|
||||||
|
| 错误码 | HTTP 状态码 | 描述 |
|
||||||
|
|-----------|-------------|------|
|
||||||
|
| `VALIDATION_ERROR` | 400 | 请求参数验证失败 |
|
||||||
|
| `FILE_UPLOAD_ERROR` | 400 | 文件上传失败 |
|
||||||
|
| `NOT_FOUND` | 404 | 资源未找到 |
|
||||||
|
| `INTERNAL_ERROR` | 500 | 内部服务器错误 |
|
||||||
|
|
||||||
|
### 频率限制
|
||||||
|
|
||||||
|
- 目前所有 API 端点均无频率限制
|
||||||
|
- 建议客户端进行请求频率控制
|
||||||
|
|
||||||
|
### CORS 支持
|
||||||
|
|
||||||
|
所有 API 端点均支持跨域访问,响应头包含:
|
||||||
|
- `Access-Control-Allow-Origin: *`
|
||||||
|
- `Access-Control-Allow-Methods: POST, GET, OPTIONS`
|
||||||
|
- `Access-Control-Allow-Headers: Content-Type, Authorization`
|
||||||
|
|
||||||
|
## 🔧 开发
|
||||||
|
|
||||||
|
### 本地开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装依赖
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# 启动开发服务器
|
||||||
|
edgeone pages dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 环境变量
|
||||||
|
|
||||||
|
项目使用以下环境变量和全局对象:
|
||||||
|
|
||||||
|
- `my_kv` - KV 存储实例,用于数据持久化
|
||||||
|
|
||||||
|
## 🛡️ 安全特性
|
||||||
|
|
||||||
|
### IP 限制(可选)
|
||||||
|
|
||||||
|
项目包含 IP 限制中间件配置(默认注释),可以限制访问来源:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
app.use('*', ipRestriction(/* 配置 */));
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 API 响应格式
|
||||||
|
|
||||||
|
### 成功响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"message": "操作成功",
|
||||||
|
"data": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误响应
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "ERROR_CODE",
|
||||||
|
"message": "错误描述"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 UI 设计
|
||||||
|
|
||||||
|
项目采用现代化 UI 设计:
|
||||||
|
- 响应式布局
|
||||||
|
- 系统字体栈
|
||||||
|
- 卡片式设计
|
||||||
|
- 统一色彩主题
|
||||||
|
- 优雅的错误页面
|
||||||
|
|
||||||
|
## 📦 依赖
|
||||||
|
|
||||||
|
- **hono** - Web 框架
|
||||||
|
- **@edgeone/ef-types** - EdgeOne Functions 类型定义
|
||||||
|
- **edgeone** - EdgeOne CLI 工具
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎提交 Issues 和 Pull Requests 来改进这个项目。
|
||||||
|
|
||||||
|
## <20><> 许可证
|
||||||
|
|
||||||
|
MIT License
|
||||||
1
functions/[[default]].ts
Normal file
1
functions/[[default]].ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { onRequest } from './index';
|
||||||
138
functions/components/Layout.tsx
Normal file
138
functions/components/Layout.tsx
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { html } from 'hono/html';
|
||||||
|
|
||||||
|
export interface SiteData {
|
||||||
|
title: string;
|
||||||
|
children?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Layout = (props: SiteData) =>
|
||||||
|
html`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>${props.title}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
||||||
|
sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
border-bottom: 2px solid #007acc;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.nav-links {
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.nav-links ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.nav-links li {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
.nav-links a {
|
||||||
|
color: #007acc;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
.nav-links a:hover {
|
||||||
|
background-color: #f0f8ff;
|
||||||
|
}
|
||||||
|
.back-link {
|
||||||
|
margin-top: 30px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
.back-link a {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.back-link a:hover {
|
||||||
|
color: #007acc;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #2c3e50;
|
||||||
|
margin-top: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
color: #34495e;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
ul li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
.highlight-box {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
.highlight-box h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
color: #495057;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">${props.children}</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Content = (props: { siteData: SiteData; name: string; children?: any }) => (
|
||||||
|
<Layout {...props.siteData}>
|
||||||
|
<h1>{props.name}</h1>
|
||||||
|
{props.children || (
|
||||||
|
<p>
|
||||||
|
Welcome to our Hono application. This page was rendered server-side with
|
||||||
|
JSX.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
26
functions/env.ts
Normal file
26
functions/env.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
interface ListResult {
|
||||||
|
complete: boolean;
|
||||||
|
cursor: string;
|
||||||
|
keys: Array<ListKey>;
|
||||||
|
}
|
||||||
|
interface ListKey {
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
declare class KVNamespace {
|
||||||
|
put(
|
||||||
|
key: string,
|
||||||
|
value: string | ArrayBuffer | ArrayBufferView | ReadableStream
|
||||||
|
): Promise<void>;
|
||||||
|
get(
|
||||||
|
key: string,
|
||||||
|
object?: { type: string }
|
||||||
|
): Promise<string | object | ArrayBuffer | ReadableStream>;
|
||||||
|
delete(key: string): Promise<void>;
|
||||||
|
list(config: {
|
||||||
|
prefix?: string;
|
||||||
|
limit?: number;
|
||||||
|
cursor?: string;
|
||||||
|
}): Promise<ListResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type { KVNamespace };
|
||||||
182
functions/index.tsx
Normal file
182
functions/index.tsx
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
import { Context, Hono } from 'hono';
|
||||||
|
// import { ipRestriction } from 'hono/ip-restriction';
|
||||||
|
import type { KVNamespace } from './env';
|
||||||
|
import { book, upload, ssr } from './routers/index';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
let my_kv: KVNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = new Hono().basePath('/');
|
||||||
|
|
||||||
|
// Register route modules
|
||||||
|
app.route('/book', book);
|
||||||
|
app.route('/upload', upload);
|
||||||
|
app.route('/ssr', ssr);
|
||||||
|
|
||||||
|
// IP restriction middleware (optional)
|
||||||
|
// app.use(
|
||||||
|
// '*',
|
||||||
|
// ipRestriction(
|
||||||
|
// c => ({
|
||||||
|
// remote: {
|
||||||
|
// // @ts-expect-error
|
||||||
|
// address: c.req.raw.eo.clientIp,
|
||||||
|
// addressType:
|
||||||
|
// String(
|
||||||
|
// // @ts-expect-error
|
||||||
|
// c.req.raw.eo.clientIp
|
||||||
|
// ).indexOf('::') === -1
|
||||||
|
// ? 'IPv4'
|
||||||
|
// : 'IPv6'
|
||||||
|
// }
|
||||||
|
// }),
|
||||||
|
// {
|
||||||
|
// denyList: [],
|
||||||
|
// allowList: [ '127.0.0.1', '::1']
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
|
||||||
|
const notFound = async (c: Context) => {
|
||||||
|
return c.html(
|
||||||
|
`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>404 - Page Not Found</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 100px auto;
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #e74c3c;
|
||||||
|
font-size: 72px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #333;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>404</h1>
|
||||||
|
<h2>Page Not Found</h2>
|
||||||
|
<p>The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.</p>
|
||||||
|
<p><a href="/">← Go back to home</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
404
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Fallback to static directory
|
||||||
|
app.notFound(async (c) => {
|
||||||
|
const url = new URL(c.req.url);
|
||||||
|
|
||||||
|
if (url.pathname === '/') {
|
||||||
|
url.pathname = '/index.html';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(url.toString(), {
|
||||||
|
headers: c.req.header()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const contentType = res.headers.get('Content-Type')!;
|
||||||
|
const body = await res.arrayBuffer();
|
||||||
|
|
||||||
|
return new Response(body, {
|
||||||
|
status: res.status,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': contentType,
|
||||||
|
'Cache-Control': 'public, max-age=3600',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return notFound(c);
|
||||||
|
}
|
||||||
|
return notFound(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.onError((err, c) => {
|
||||||
|
return c.html(
|
||||||
|
`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>500 - Internal Server Error</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 100px auto;
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #e74c3c;
|
||||||
|
font-size: 72px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
color: #333;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>500</h1>
|
||||||
|
<h2>Internal Server Error</h2>
|
||||||
|
<p>Something went wrong on our server. Please try again later.</p>
|
||||||
|
<p>Error: ${err.message}</p>
|
||||||
|
<p><a href="/">← Go back to home</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`,
|
||||||
|
500
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// EdgeOne Functions export
|
||||||
|
export function onRequest(context: {
|
||||||
|
request: Request;
|
||||||
|
params: Record<string, string>;
|
||||||
|
env: Record<string, any>;
|
||||||
|
}): Response | Promise<Response> {
|
||||||
|
return app.fetch(context.request, context.env);
|
||||||
|
}
|
||||||
105
functions/routers/book.tsx
Normal file
105
functions/routers/book.tsx
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import { Hono } from 'hono';
|
||||||
|
import { Content } from '../components/Layout';
|
||||||
|
|
||||||
|
const book = new Hono();
|
||||||
|
|
||||||
|
// Get all books list
|
||||||
|
book.get('/', (c) => {
|
||||||
|
const props = {
|
||||||
|
name: '📚 Book Library',
|
||||||
|
siteData: {
|
||||||
|
title: 'Books - Hono App',
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
<p>Browse our collection of books</p>
|
||||||
|
<div style="margin: 20px 0;">
|
||||||
|
<div style="padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #007acc;">
|
||||||
|
<h3><a href="/book/1">The Art of Programming</a></h3>
|
||||||
|
<p>A comprehensive guide to software development</p>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #007acc;">
|
||||||
|
<h3><a href="/book/2">JavaScript: The Good Parts</a></h3>
|
||||||
|
<p>Essential JavaScript programming techniques</p>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 15px; margin: 10px 0; background: #f8f9fa; border-radius: 5px; border-left: 4px solid #007acc;">
|
||||||
|
<h3><a href="/book/3">Clean Code</a></h3>
|
||||||
|
<p>Writing maintainable and readable code</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="back-link">
|
||||||
|
<a href="/">← Back to home</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return c.html(Content(props));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get specific book
|
||||||
|
book.get('/:id', (c) => {
|
||||||
|
const id = c.req.param('id');
|
||||||
|
const books: Record<string, any> = {
|
||||||
|
'1': { title: 'The Art of Programming', author: 'John Doe', description: 'A comprehensive guide to software development principles and practices.' },
|
||||||
|
'2': { title: 'JavaScript: The Good Parts', author: 'Douglas Crockford', description: 'Essential JavaScript programming techniques and best practices.' },
|
||||||
|
'3': { title: 'Clean Code', author: 'Robert C. Martin', description: 'A handbook of agile software craftsmanship.' }
|
||||||
|
};
|
||||||
|
|
||||||
|
const bookData = books[id];
|
||||||
|
if (!bookData) {
|
||||||
|
const props = {
|
||||||
|
name: 'Book Not Found',
|
||||||
|
siteData: {
|
||||||
|
title: 'Book Not Found',
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
<p>The book with ID {id} was not found.</p>
|
||||||
|
<div class="back-link">
|
||||||
|
<a href="/book">← Back to books</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
return c.html(Content(props), 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = {
|
||||||
|
name: `📖 ${bookData.title}`,
|
||||||
|
siteData: {
|
||||||
|
title: `${bookData.title} - Book Details`,
|
||||||
|
},
|
||||||
|
children: (
|
||||||
|
<div>
|
||||||
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0;">
|
||||||
|
<p><strong>Author:</strong> {bookData.author}</p>
|
||||||
|
<p><strong>Book ID:</strong> {id}</p>
|
||||||
|
<p><strong>Description:</strong> {bookData.description}</p>
|
||||||
|
</div>
|
||||||
|
<div class="back-link">
|
||||||
|
<a href="/book">← Back to books</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return c.html(Content(props));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create new book (API endpoint)
|
||||||
|
book.post('/', async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Book created successfully',
|
||||||
|
book: {
|
||||||
|
id: Math.random().toString(36).substr(2, 9),
|
||||||
|
title: body.title || 'Untitled',
|
||||||
|
author: body.author || 'Unknown',
|
||||||
|
createdAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default book;
|
||||||
3
functions/routers/index.ts
Normal file
3
functions/routers/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as book } from './book';
|
||||||
|
export { default as upload } from './upload';
|
||||||
|
export { default as ssr } from './ssr';
|
||||||
18
functions/routers/ssr.tsx
Normal file
18
functions/routers/ssr.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Hono } from 'hono';
|
||||||
|
import { Layout, Content } from '../components/Layout';
|
||||||
|
|
||||||
|
const ssr = new Hono();
|
||||||
|
|
||||||
|
// Dynamic page route
|
||||||
|
ssr.get('/:name', (c) => {
|
||||||
|
const { name } = c.req.param();
|
||||||
|
const props = {
|
||||||
|
name: name,
|
||||||
|
siteData: {
|
||||||
|
title: `Hello ${name} - JSX Sample`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return c.html(<Content {...props} />);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ssr;
|
||||||
17
functions/routers/upload.ts
Normal file
17
functions/routers/upload.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { Hono } from 'hono';
|
||||||
|
|
||||||
|
const upload = new Hono();
|
||||||
|
|
||||||
|
// File upload endpoint
|
||||||
|
upload.post('/', async (c) => {
|
||||||
|
const body = await c.req.parseBody();
|
||||||
|
console.log(body['file'], 'File uploaded');
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
success: true,
|
||||||
|
message: 'File uploaded successfully',
|
||||||
|
fileName: body['file']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default upload;
|
||||||
5755
package-lock.json
generated
Normal file
5755
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "edgeone-pages-hono-app",
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "rm -rf ./node_modules && edgeone pages deploy ./ -n edgeone-hono-app -t ",
|
||||||
|
"sync-docs": "node scripts/sync-docs.js",
|
||||||
|
"build": "npm run sync-docs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
|
"hono": "^4.7.10",
|
||||||
|
"marked": "^15.0.12",
|
||||||
|
"marked-highlight": "^2.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@edgeone/ef-types": "^1.0.5",
|
||||||
|
"edgeone": "^1.0.21"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
345
public/index.html
Normal file
345
public/index.html
Normal file
@@ -0,0 +1,345 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>EdgeOne Pages Hono Application</title>
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
<link rel="icon" href="./favicon.ico" type="image/x-icon">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1>EdgeOne Pages Hono Application</h1>
|
||||||
|
<p>This is a modern Web application built on the <a href="https://hono.dev/" target="_blank" rel="noopener noreferrer">Hono</a> framework, deployed on the EdgeOne Pages platform.</p>
|
||||||
|
<p>Live demo: <a href="https://hono.edgeone.app" target="_blank" rel="noopener noreferrer">https://hono.edgeone.app</a></p>
|
||||||
|
<h2>Deploy</h2>
|
||||||
|
<p><a href="https://edgeone.ai/pages/new?from=github&template=hono" target="_blank" rel="noopener noreferrer"><img src="https://cdnstatic.tencentcs.com/edgeone/pages/deploy.svg" alt="Deploy with EdgeOne Pages" /></a></p>
|
||||||
|
<h2>🚀 Project Features</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Modular Route Design</strong> - Clear route organization structure</li>
|
||||||
|
<li><strong>Server-Side Rendering</strong> - Page rendering using JSX and HTML templates</li>
|
||||||
|
<li><strong>File Upload</strong> - File upload functionality support</li>
|
||||||
|
<li><strong>Book Management</strong> - Example CRUD operations</li>
|
||||||
|
<li><strong>Error Handling</strong> - Beautiful 404 and 500 error pages</li>
|
||||||
|
<li><strong>TypeScript Support</strong> - Complete type definitions</li>
|
||||||
|
</ul>
|
||||||
|
<h2>📁 Project Structure</h2>
|
||||||
|
<pre><code>functions/
|
||||||
|
├── index.tsx # Main entry file
|
||||||
|
├── [[default]].ts # EdgeOne Functions default route
|
||||||
|
├── env.ts # Environment type definitions
|
||||||
|
├── components/ # Components directory
|
||||||
|
│ └── Layout.tsx # Page layout component
|
||||||
|
└── routers/ # Route modules
|
||||||
|
├── index.ts # Unified route exports
|
||||||
|
├── book.tsx # Book related routes
|
||||||
|
├── ssr.tsx # Server-side rendering routes
|
||||||
|
└── upload.ts # File upload routes
|
||||||
|
</code></pre>
|
||||||
|
<h2>🛣️ Route Details</h2>
|
||||||
|
<h3>Static Routes</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Method</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td><code>/</code></td>
|
||||||
|
<td>GET</td>
|
||||||
|
<td>Static home page, serves <code>index.html</code> from public directory</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
<p><strong>Examples:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><code>https://hono.edgeone.app/</code> - Static home page</li>
|
||||||
|
</ul>
|
||||||
|
<h3>SSR Routes (<code>/ssr</code>)</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Method</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td><code>/ssr/:name</code></td>
|
||||||
|
<td>GET</td>
|
||||||
|
<td>Dynamic SSR page, displays personalized welcome message</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
<p><strong>Examples:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><code>https://hono.edgeone.app/ssr/john</code> - Shows "Hello john!" page</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Book Management Routes (<code>/book</code>)</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Method</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td><code>/book</code></td>
|
||||||
|
<td>GET</td>
|
||||||
|
<td>Get all books list page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/book/:id</code></td>
|
||||||
|
<td>GET</td>
|
||||||
|
<td>Get specific book details page</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>/book</code></td>
|
||||||
|
<td>POST</td>
|
||||||
|
<td>Create new book (API endpoint)</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
<p><strong>Examples:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li><code>https://hono.edgeone.app/book</code> - Book list</li>
|
||||||
|
<li><code>https://hono.edgeone.app/book/1</code> - Details of the first book</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Create Book API Request Example:</strong></p>
|
||||||
|
<pre><code class="language-bash">curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||||
|
-d <span class="hljs-string">'{
|
||||||
|
"title": "New Book Title",
|
||||||
|
"author": "Author Name"
|
||||||
|
}'</span>
|
||||||
|
</code></pre>
|
||||||
|
<p><strong>Supported Features:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<li>CORS cross-origin support</li>
|
||||||
|
</ul>
|
||||||
|
<h3>File Upload Routes (<code>/upload</code>)</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Path</th>
|
||||||
|
<th>Method</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td><code>/upload</code></td>
|
||||||
|
<td>POST</td>
|
||||||
|
<td>File upload endpoint</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
<p><strong>Example:</strong></p>
|
||||||
|
<pre><code class="language-bash">curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F <span class="hljs-string">"file=@example.txt"</span>
|
||||||
|
</code></pre>
|
||||||
|
<h2>📖 Detailed API Documentation</h2>
|
||||||
|
<h3>Basic Information</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Base URL</strong>: <code>https://hono.edgeone.app</code></li>
|
||||||
|
<li><strong>Content-Type</strong>: <code>application/json</code></li>
|
||||||
|
<li><strong>Encoding</strong>: UTF-8</li>
|
||||||
|
</ul>
|
||||||
|
<h3>API Details</h3>
|
||||||
|
<h4>1. File Upload</h4>
|
||||||
|
<p><strong>Endpoint</strong>: <code>POST /upload</code></p>
|
||||||
|
<p><strong>Description</strong>: Upload files to server</p>
|
||||||
|
<p><strong>Request Format</strong>: <code>multipart/form-data</code></p>
|
||||||
|
<p><strong>Request Parameters</strong>:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>file</code> (required): File to upload</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>curl Request Examples</strong>:</p>
|
||||||
|
<pre><code class="language-bash"><span class="hljs-comment"># Upload text file</span>
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F <span class="hljs-string">"file=@/path/to/your/file.txt"</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Upload image file</span>
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F <span class="hljs-string">"file=@/path/to/image.jpg"</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Upload with custom filename</span>
|
||||||
|
curl -X POST https://hono.edgeone.app/upload \
|
||||||
|
-F <span class="hljs-string">"file=@document.pdf;filename=my-document.pdf"</span>
|
||||||
|
</code></pre>
|
||||||
|
<p><strong>Response Example</strong>:</p>
|
||||||
|
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"File uploaded successfully"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"fileName"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"file.txt"</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
</code></pre>
|
||||||
|
<p><strong>Error Response</strong>:</p>
|
||||||
|
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"No file provided"</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
</code></pre>
|
||||||
|
<h4>2. Create Book</h4>
|
||||||
|
<p><strong>Endpoint</strong>: <code>POST /book</code></p>
|
||||||
|
<p><strong>Description</strong>: Create new book record</p>
|
||||||
|
<p><strong>Request Parameters</strong>:</p>
|
||||||
|
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Book Title"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"author"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Author Name"</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
</code></pre>
|
||||||
|
<p><strong>Parameter Description</strong>:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>title</code> (optional): Book title, defaults to "Untitled"</li>
|
||||||
|
<li><code>author</code> (optional): Author name, defaults to "Unknown"</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>curl Request Examples</strong>:</p>
|
||||||
|
<pre><code class="language-bash"><span class="hljs-comment"># Create book with complete information</span>
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||||
|
-d <span class="hljs-string">'{
|
||||||
|
"title": "Dream of the Red Chamber",
|
||||||
|
"author": "Cao Xueqin"
|
||||||
|
}'</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Create book with only title</span>
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||||
|
-d <span class="hljs-string">'{
|
||||||
|
"title": "New Book Title"
|
||||||
|
}'</span>
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Create empty book (using defaults)</span>
|
||||||
|
curl -X POST https://hono.edgeone.app/book \
|
||||||
|
-H <span class="hljs-string">"Content-Type: application/json"</span> \
|
||||||
|
-d <span class="hljs-string">'{}'</span>
|
||||||
|
</code></pre>
|
||||||
|
<p><strong>Response Example</strong>:</p>
|
||||||
|
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Book created successfully"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"book"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"id"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"abc123def"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"title"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Book Title"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"author"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Author Name"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"createdAt"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"2023-12-01T10:00:00.000Z"</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
</code></pre>
|
||||||
|
<h4>3. Get Book Information</h4>
|
||||||
|
<p><strong>curl Request Examples</strong>:</p>
|
||||||
|
<pre><code class="language-bash"><span class="hljs-comment"># Get all books list</span>
|
||||||
|
curl -X GET https://hono.edgeone.app/book
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Get specific book details</span>
|
||||||
|
curl -X GET https://hono.edgeone.app/book/1
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Get personal page</span>
|
||||||
|
curl -X GET https://hono.edgeone.app/john
|
||||||
|
</code></pre>
|
||||||
|
<h3>Error Code Description</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Error Code</th>
|
||||||
|
<th>HTTP Status Code</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody><tr>
|
||||||
|
<td><code>VALIDATION_ERROR</code></td>
|
||||||
|
<td>400</td>
|
||||||
|
<td>Request parameter validation failed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>FILE_UPLOAD_ERROR</code></td>
|
||||||
|
<td>400</td>
|
||||||
|
<td>File upload failed</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>NOT_FOUND</code></td>
|
||||||
|
<td>404</td>
|
||||||
|
<td>Resource not found</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>INTERNAL_ERROR</code></td>
|
||||||
|
<td>500</td>
|
||||||
|
<td>Internal server error</td>
|
||||||
|
</tr>
|
||||||
|
</tbody></table>
|
||||||
|
<h3>Rate Limiting</h3>
|
||||||
|
<ul>
|
||||||
|
<li>All API endpoints currently have no rate limiting</li>
|
||||||
|
<li>Client-side request frequency control is recommended</li>
|
||||||
|
</ul>
|
||||||
|
<h3>CORS Support</h3>
|
||||||
|
<p>All API endpoints support cross-origin access, response headers include:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>Access-Control-Allow-Origin: *</code></li>
|
||||||
|
<li><code>Access-Control-Allow-Methods: POST, GET, OPTIONS</code></li>
|
||||||
|
<li><code>Access-Control-Allow-Headers: Content-Type, Authorization</code></li>
|
||||||
|
</ul>
|
||||||
|
<h2>🔧 Development</h2>
|
||||||
|
<h3>Local Development</h3>
|
||||||
|
<pre><code class="language-bash"><span class="hljs-comment"># Install dependencies</span>
|
||||||
|
npm install
|
||||||
|
|
||||||
|
<span class="hljs-comment"># Start development server</span>
|
||||||
|
npm run dev
|
||||||
|
</code></pre>
|
||||||
|
<h3>Deployment</h3>
|
||||||
|
<pre><code class="language-bash"><span class="hljs-comment"># Deploy to EdgeOne</span>
|
||||||
|
npm run deploy
|
||||||
|
</code></pre>
|
||||||
|
<h2>🌐 Environment Variables</h2>
|
||||||
|
<p>The project uses the following environment variables and global objects:</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>my_kv</code> - KV storage instance for data persistence</li>
|
||||||
|
</ul>
|
||||||
|
<h2>🛡️ Security Features</h2>
|
||||||
|
<h3>IP Restriction (Optional)</h3>
|
||||||
|
<p>The project includes IP restriction middleware configuration (commented by default), which can limit access sources:</p>
|
||||||
|
<pre><code class="language-typescript">app.<span class="hljs-title function_">use</span>(<span class="hljs-string">'*'</span>, <span class="hljs-title function_">ipRestriction</span>(<span class="hljs-comment">/* configuration */</span>));
|
||||||
|
</code></pre>
|
||||||
|
<h2>📝 API Response Format</h2>
|
||||||
|
<h3>Success Response</h3>
|
||||||
|
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"success"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Operation successful"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"data"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
</code></pre>
|
||||||
|
<h3>Error Response</h3>
|
||||||
|
<pre><code class="language-json"><span class="hljs-punctuation">{</span>
|
||||||
|
<span class="hljs-attr">"error"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"ERROR_CODE"</span><span class="hljs-punctuation">,</span>
|
||||||
|
<span class="hljs-attr">"message"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"Error description"</span>
|
||||||
|
<span class="hljs-punctuation">}</span>
|
||||||
|
</code></pre>
|
||||||
|
<h2>🎨 UI Design</h2>
|
||||||
|
<p>The project adopts modern UI design:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Responsive layout</li>
|
||||||
|
<li>System font stack</li>
|
||||||
|
<li>Card-style design</li>
|
||||||
|
<li>Unified color theme</li>
|
||||||
|
<li>Elegant error pages</li>
|
||||||
|
</ul>
|
||||||
|
<h2>📦 Dependencies</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>hono</strong> - Web framework</li>
|
||||||
|
<li><strong>@edgeone/ef-types</strong> - EdgeOne Functions type definitions</li>
|
||||||
|
<li><strong>edgeone</strong> - EdgeOne CLI tool</li>
|
||||||
|
</ul>
|
||||||
|
<h2>🤝 Contributing</h2>
|
||||||
|
<p>Welcome to submit Issues and Pull Requests to improve this project.</p>
|
||||||
|
<h2>📄 License</h2>
|
||||||
|
<p>MIT License</p>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="highlight-box">
|
||||||
|
<h3>📝 Development Notes</h3>
|
||||||
|
<p>This is a complete example application demonstrating various features and best practices of the Hono framework on EdgeOne Functions.</p>
|
||||||
|
<p>The project structure is clear, code is well organized, and suitable as a starting template for other projects.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
244
public/style.css
Normal file
244
public/style.css
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
|
||||||
|
sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #1a202c;
|
||||||
|
border-bottom: 3px solid #3182ce;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: #2d3748;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid #e2e8f0;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #4a5568;
|
||||||
|
margin-top: 30px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
color: #718096;
|
||||||
|
margin-top: 25px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #3182ce;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1px solid transparent;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #2c5282;
|
||||||
|
border-bottom-color: #3182ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
padding-left: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表格样式 */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 20px 0;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #4a5568;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 16px;
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f7fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #e53e3e;
|
||||||
|
padding: 3px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: #1a202c;
|
||||||
|
color: #e2e8f0;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 20px 0;
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
border: 1px solid #2d3748;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
background: #1a202c !important;
|
||||||
|
color: #e2e8f0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box {
|
||||||
|
background: linear-gradient(135deg, #e6fffa 0%, #f0fff4 100%);
|
||||||
|
border-left: 4px solid #38a169;
|
||||||
|
padding: 24px;
|
||||||
|
margin: 30px 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(56, 161, 105, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box h3 {
|
||||||
|
color: #2f855a;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box p {
|
||||||
|
color: #2d3748;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-box p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #cbd5e0;
|
||||||
|
padding-left: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: #4a5568;
|
||||||
|
background: #f7fafc;
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 8px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: #cbd5e0;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a0aec0;
|
||||||
|
}
|
||||||
124
scripts/sync-docs.js
Normal file
124
scripts/sync-docs.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { marked } = require('marked');
|
||||||
|
const { markedHighlight } = require('marked-highlight');
|
||||||
|
const hljs = require('highlight.js');
|
||||||
|
|
||||||
|
marked.use(markedHighlight({
|
||||||
|
langPrefix: 'hljs language-',
|
||||||
|
highlight(code, lang) {
|
||||||
|
const language = hljs.getLanguage(lang) ? lang : 'plaintext';
|
||||||
|
return hljs.highlight(code, { language }).value;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const renderer = new marked.Renderer();
|
||||||
|
renderer.link = function(token) {
|
||||||
|
const href = token.href;
|
||||||
|
const title = token.title;
|
||||||
|
const text = token.text;
|
||||||
|
const titleAttr = title ? ` title="${title}"` : '';
|
||||||
|
|
||||||
|
if (text.includes('<img') || text.match(/^!\[.*\]\(.*\)$/)) {
|
||||||
|
if (text.match(/^!\[(.*?)\]\((.*?)\)$/)) {
|
||||||
|
const imgMatch = text.match(/^!\[(.*?)\]\((.*?)\)$/);
|
||||||
|
const altText = imgMatch[1];
|
||||||
|
const imgSrc = imgMatch[2];
|
||||||
|
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer"><img src="${imgSrc}" alt="${altText}" /></a>`;
|
||||||
|
}
|
||||||
|
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `<a href="${href}"${titleAttr} target="_blank" rel="noopener noreferrer">${text}</a>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
marked.setOptions({
|
||||||
|
breaks: false,
|
||||||
|
gfm: true,
|
||||||
|
headerIds: true,
|
||||||
|
mangle: false,
|
||||||
|
pedantic: false,
|
||||||
|
sanitize: false,
|
||||||
|
silent: false,
|
||||||
|
smartLists: true,
|
||||||
|
smartypants: false,
|
||||||
|
xhtml: false,
|
||||||
|
renderer: renderer
|
||||||
|
});
|
||||||
|
|
||||||
|
const readmePath = path.join(__dirname, '../README.md');
|
||||||
|
const readmeContent = fs.readFileSync(readmePath, 'utf-8');
|
||||||
|
|
||||||
|
function parseReadme(content) {
|
||||||
|
const titleMatch = content.match(/^# (.+)$/m);
|
||||||
|
const title = titleMatch ? titleMatch[1] : 'EdgeOne Pages Hono Application';
|
||||||
|
|
||||||
|
const descMatch = content.match(/This is a modern Web application[^\n]+/);
|
||||||
|
const description = descMatch ? descMatch[0] : '';
|
||||||
|
|
||||||
|
const liveDemoMatch = content.match(/Live demo: (.+)$/m);
|
||||||
|
const liveDemo = liveDemoMatch ? liveDemoMatch[0] : '';
|
||||||
|
|
||||||
|
const hasDeployButton = content.includes('Deploy with EdgeOne Pages');
|
||||||
|
|
||||||
|
const htmlContent = marked(content);
|
||||||
|
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
liveDemo,
|
||||||
|
hasDeployButton,
|
||||||
|
htmlContent
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateHTML(data) {
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>${data.title}</title>
|
||||||
|
<link rel="stylesheet" href="./style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
${data.htmlContent}
|
||||||
|
|
||||||
|
<div class="highlight-box">
|
||||||
|
<h3>📝 Development Notes</h3>
|
||||||
|
<p>This is a complete example application demonstrating various features and best practices of the Hono framework on EdgeOne Functions.</p>
|
||||||
|
<p>The project structure is clear, code is well organized, and suitable as a starting template for other projects.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
try {
|
||||||
|
console.log('📖 Reading README.md...');
|
||||||
|
const parsedData = parseReadme(readmeContent);
|
||||||
|
|
||||||
|
console.log('🔄 Generating HTML with marked...');
|
||||||
|
const htmlContent = generateHTML(parsedData);
|
||||||
|
|
||||||
|
console.log('💾 Writing to public/index.html...');
|
||||||
|
const outputPath = path.join(__dirname, '../public/index.html');
|
||||||
|
fs.writeFileSync(outputPath, htmlContent, 'utf-8');
|
||||||
|
|
||||||
|
console.log('✅ Successfully converted README.md to HTML using marked');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error converting docs:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { parseReadme, generateHTML };
|
||||||
15
tsconfig.json
Normal file
15
tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"lib": [
|
||||||
|
"ESNext"
|
||||||
|
],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "hono/jsx",
|
||||||
|
"types": ["@edgeone/ef-types"]
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user