parent
1f42e5d713
commit
2995cdb3e2
|
|
@ -0,0 +1,232 @@
|
|||
# H5海报项目 API 文档
|
||||
|
||||
本文档描述了H5海报项目的后端API接口,供前端开发使用。
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **Base URL**: `http://localhost:8080` (开发环境) / 生产环境使用相对路径
|
||||
- **Content-Type**: `application/json`
|
||||
- **超时时间**: 60秒
|
||||
|
||||
## API端点
|
||||
|
||||
### 1. 页面访问记录 - `/api/page-visit`
|
||||
|
||||
记录用户页面访问信息,用于统计分析。
|
||||
|
||||
#### 请求信息
|
||||
- **方法**: `POST`
|
||||
- **路径**: `/api/page-visit`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必需 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| user_agent | string | 是 | 用户浏览器User-Agent字符串 |
|
||||
| page_name | string | 是 | 页面名称,用于标识访问的页面 |
|
||||
|
||||
#### 请求示例
|
||||
```json
|
||||
{
|
||||
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X)",
|
||||
"page_name": "home"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应信息
|
||||
|
||||
**成功响应** (200 OK)
|
||||
```json
|
||||
{
|
||||
"message": "Page visit recorded successfully",
|
||||
"id": 123
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (400 Bad Request)
|
||||
```json
|
||||
{
|
||||
"error": "Invalid request data"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (500 Internal Server Error)
|
||||
```json
|
||||
{
|
||||
"error": "Database not available"
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用说明
|
||||
- 每次用户访问页面时调用此API
|
||||
- 系统会自动记录用户的IP地址
|
||||
- 如果数据库不可用,API会返回500错误
|
||||
- 建议在页面加载完成后异步调用,不影响用户体验
|
||||
|
||||
---
|
||||
|
||||
### 2. 用户信息保存 - `/api/user-info`
|
||||
|
||||
保存用户填写的个人信息,用于活动参与或后续联系。
|
||||
|
||||
#### 请求信息
|
||||
- **方法**: `POST`
|
||||
- **路径**: `/api/user-info`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必需 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| name | string | 是 | 用户姓名 |
|
||||
| phone | string | 是 | 用户手机号码 |
|
||||
| address | string | 是 | 用户地址 |
|
||||
| msg | string | 是 | 用户留言或备注信息,最多200字 |
|
||||
|
||||
#### 请求示例
|
||||
```json
|
||||
{
|
||||
"name": "张三",
|
||||
"phone": "13800138000",
|
||||
"address": "北京市朝阳区xxx街道xxx号",
|
||||
"msg": "希望能够获得精美礼品,谢谢!"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应信息
|
||||
|
||||
**成功响应** (200 OK)
|
||||
```json
|
||||
{
|
||||
"message": "User info saved successfully",
|
||||
"id": 456
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (400 Bad Request)
|
||||
```json
|
||||
{
|
||||
"error": "Invalid request data"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (500 Internal Server Error)
|
||||
```json
|
||||
{
|
||||
"error": "Database not available"
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用说明
|
||||
- 用户在表单中填写信息后提交到此API
|
||||
- 所有字段都是必填项
|
||||
- msg字段最多支持200个字符,用于用户留言或备注
|
||||
- 系统会自动记录创建时间
|
||||
- 建议在前端进行基础的数据验证(如手机号格式)
|
||||
|
||||
---
|
||||
|
||||
### 3. 对联海报生成 - `/api/couplets`
|
||||
|
||||
根据用户输入的两个汉字,使用AI生成对联并创建海报。
|
||||
|
||||
#### 请求信息
|
||||
- **方法**: `POST`
|
||||
- **路径**: `/api/couplets`
|
||||
|
||||
#### 请求参数
|
||||
|
||||
| 参数名 | 类型 | 必需 | 描述 |
|
||||
|--------|------|------|------|
|
||||
| title | string | 是 | 两个汉字,用于生成对联(如"新春") |
|
||||
|
||||
#### 请求示例
|
||||
```json
|
||||
{
|
||||
"title": "新春"
|
||||
}
|
||||
```
|
||||
|
||||
#### 响应信息
|
||||
|
||||
**成功响应** (200 OK)
|
||||
```json
|
||||
{
|
||||
"share_url": "http://xcsq.wxinh5.host/share/abc123def456",
|
||||
"poster_id": "abc123def456",
|
||||
"image_url": "http://xcsq.wxinh5.host/posters/couplet_abc123def456.png"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (400 Bad Request)
|
||||
```json
|
||||
{
|
||||
"error": "Invalid request data"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应** (500 Internal Server Error)
|
||||
```json
|
||||
{
|
||||
"error": "Failed to generate couplet poster: API key is not configured"
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用说明
|
||||
- 用户需要输入两个汉字作为对联生成的主题
|
||||
- 系统会调用AI生成对联(上联、下联、横批)
|
||||
- 生成的对联会制作成海报图片
|
||||
- 返回的`image_url`可以直接用于显示海报
|
||||
- 返回的`share_url`可以用于分享功能
|
||||
- 如果AI服务不可用,系统会使用默认对联
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
所有API在出错时都会返回相应的HTTP状态码和错误信息:
|
||||
|
||||
- **400 Bad Request**: 请求参数错误或格式不正确
|
||||
- **500 Internal Server Error**: 服务器内部错误或数据库不可用
|
||||
|
||||
错误响应格式:
|
||||
```json
|
||||
{
|
||||
"error": "错误描述信息"
|
||||
}
|
||||
```
|
||||
|
||||
## 使用建议
|
||||
|
||||
1. **错误处理**: 前端应该适当处理API返回的错误信息,给用户友好的提示
|
||||
2. **重试机制**: 对于网络错误,可以考虑实现重试机制
|
||||
3. **超时处理**: 设置合适的超时时间,避免用户长时间等待
|
||||
4. **异步调用**: 建议使用异步方式调用API,不影响用户体验
|
||||
|
||||
## 前端使用示例
|
||||
|
||||
可以参考项目中的API封装文件:`frontend/src/utils/api.js`
|
||||
|
||||
```javascript
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:8080',
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 使用示例
|
||||
const recordVisit = async () => {
|
||||
try {
|
||||
const response = await api.post('/api/page-visit', {
|
||||
user_agent: navigator.userAgent,
|
||||
page_name: 'current_page'
|
||||
})
|
||||
console.log('Visit recorded:', response.data)
|
||||
} catch (error) {
|
||||
console.error('Failed to record visit:', error)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import axios from 'axios'
|
||||
|
||||
const API_BASE_URL = import.meta.env.PROD ? '' : 'http://localhost:8080'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 60000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
export const generatePoster = async (posterData) => {
|
||||
try {
|
||||
const response = await api.post('/api/posters', posterData)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || '生成海报失败')
|
||||
}
|
||||
}
|
||||
|
||||
export const getPoster = async (posterId) => {
|
||||
try {
|
||||
const response = await api.get(`/api/posters/${posterId}`)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || '获取海报失败')
|
||||
}
|
||||
}
|
||||
|
||||
export const generateCoupletPoster = async (coupletData) => {
|
||||
try {
|
||||
const response = await api.post('/api/couplets', coupletData)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || '生成对联海报失败')
|
||||
}
|
||||
}
|
||||
|
||||
export const saveUserInfo = async (userInfo) => {
|
||||
try {
|
||||
const response = await api.post('/api/user-info', userInfo)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
throw new Error(error.response?.data?.error || '保存用户信息失败')
|
||||
}
|
||||
}
|
||||
|
||||
export const recordPageVisit = async (pageVisitData) => {
|
||||
try {
|
||||
const response = await api.post('/api/page-visit', pageVisitData)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
console.error('Failed to record page visit:', error)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* API 请求封装
|
||||
* 基于 uni.request 封装,支持拦截器、错误处理等
|
||||
*/
|
||||
|
||||
// API 基础配置
|
||||
const BASE_URL = 'http://xcsq.wxinh5.host'
|
||||
|
||||
// 请求拦截器
|
||||
const requestInterceptor = (config) => {
|
||||
// 可以在这里添加全局请求头、token 等
|
||||
return config
|
||||
}
|
||||
|
||||
// 响应拦截器
|
||||
const responseInterceptor = (response) => {
|
||||
// 统一处理响应数据
|
||||
return response
|
||||
}
|
||||
|
||||
// 错误处理
|
||||
const handleError = (error) => {
|
||||
console.error('API 请求错误:', error)
|
||||
|
||||
// 根据错误类型进行处理
|
||||
if (error.errMsg && error.errMsg.includes('timeout')) {
|
||||
uni.showToast({
|
||||
title: '请求超时,请稍后重试',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
} else if (error.errMsg && error.errMsg.includes('fail')) {
|
||||
uni.showToast({
|
||||
title: '网络请求失败,请检查网络',
|
||||
icon: 'none',
|
||||
duration: 2000
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用请求方法
|
||||
* @param {Object} options - 请求配置
|
||||
* @returns {Promise} - 返回 Promise
|
||||
*/
|
||||
export const request = (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 合并配置
|
||||
const config = {
|
||||
url: '',
|
||||
method: 'POST',
|
||||
data: {},
|
||||
header: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
timeout: 10000,
|
||||
...options
|
||||
}
|
||||
|
||||
// 处理完整 URL
|
||||
if (!config.url.startsWith('http')) {
|
||||
config.url = `${BASE_URL}${config.url}`
|
||||
}
|
||||
|
||||
// 应用请求拦截器
|
||||
const finalConfig = requestInterceptor(config)
|
||||
|
||||
// 执行请求
|
||||
uni.request({
|
||||
...finalConfig,
|
||||
success: (response) => {
|
||||
// 应用响应拦截器
|
||||
const result = responseInterceptor(response)
|
||||
|
||||
// 根据 HTTP 状态码处理
|
||||
if (response.statusCode >= 200 && response.statusCode < 300) {
|
||||
resolve(result.data)
|
||||
} else {
|
||||
reject(new Error(`HTTP ${response.statusCode}: ${response.errMsg || '请求失败'}`))
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
handleError(error).catch(reject)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
* @param {string} url - 请求地址
|
||||
* @param {Object} params - 请求参数
|
||||
* @param {Object} options - 其他配置
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const get = (url, params = {}, options = {}) => {
|
||||
return request({
|
||||
url,
|
||||
method: 'GET',
|
||||
data: params,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
* @param {string} url - 请求地址
|
||||
* @param {Object} data - 请求数据
|
||||
* @param {Object} options - 其他配置
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const post = (url, data = {}, options = {}) => {
|
||||
return request({
|
||||
url,
|
||||
method: 'POST',
|
||||
data,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 请求
|
||||
* @param {string} url - 请求地址
|
||||
* @param {Object} data - 请求数据
|
||||
* @param {Object} options - 其他配置
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const put = (url, data = {}, options = {}) => {
|
||||
return request({
|
||||
url,
|
||||
method: 'PUT',
|
||||
data,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求
|
||||
* @param {string} url - 请求地址
|
||||
* @param {Object} params - 请求参数
|
||||
* @param {Object} options - 其他配置
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const del = (url, params = {}, options = {}) => {
|
||||
return request({
|
||||
url,
|
||||
method: 'DELETE',
|
||||
data: params,
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
del
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* 页面访问相关 API
|
||||
*/
|
||||
import { post } from './request.js'
|
||||
|
||||
/**
|
||||
* 记录页面访问
|
||||
* @param {Object} data - 访问数据
|
||||
* @param {string} data.page - 页面标识
|
||||
* @param {string} data.source - 访问来源
|
||||
* @param {Object} data.extra - 额外数据
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export const recordPageVisit = (data = {}) => {
|
||||
const defaultData = {
|
||||
page: 'index',
|
||||
timestamp: Date.now(),
|
||||
...data
|
||||
}
|
||||
|
||||
return post('/api/page-visit', defaultData)
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
recordPageVisit
|
||||
}
|
||||
|
|
@ -109,10 +109,10 @@ onMounted(() => {
|
|||
transform: translateX(-50%) rotate(0deg);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-50%) rotate(-3deg);
|
||||
transform: translateX(-50%) rotate(-6deg);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(-50%) rotate(3deg);
|
||||
transform: translateX(-50%) rotate(6deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
import { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
|
||||
import { useSceneStore } from '../store/scene'
|
||||
import { useCollectionStore } from '../store/collection'
|
||||
import { recordPageVisit } from '../api/visit.js'
|
||||
import LongImageViewer from './LongImageViewer.vue'
|
||||
import MediaPlayer from './MediaPlayer.vue'
|
||||
import QianmenScene from './QianmenScene.vue'
|
||||
|
|
@ -146,6 +147,12 @@ const collectedSeals = computed(() => {
|
|||
|
||||
// 组件挂载后初始化
|
||||
onMounted(() => {
|
||||
// 记录页面访问
|
||||
recordPageVisit({
|
||||
user_agent: navigator.userAgent,
|
||||
page_name: 'home'
|
||||
})
|
||||
|
||||
// 检查并初始化场景交互状态
|
||||
scenes.value.forEach((scene, index) => {
|
||||
if (index > 0 && index < scenes.value.length) {
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 239 KiB After Width: | Height: | Size: 242 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 448 KiB After Width: | Height: | Size: 447 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 612 KiB After Width: | Height: | Size: 535 KiB |
Loading…
Reference in New Issue