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);
|
transform: translateX(-50%) rotate(0deg);
|
||||||
}
|
}
|
||||||
25% {
|
25% {
|
||||||
transform: translateX(-50%) rotate(-3deg);
|
transform: translateX(-50%) rotate(-6deg);
|
||||||
}
|
}
|
||||||
75% {
|
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 { ref, onMounted, onUnmounted, computed, watch, nextTick } from 'vue'
|
||||||
import { useSceneStore } from '../store/scene'
|
import { useSceneStore } from '../store/scene'
|
||||||
import { useCollectionStore } from '../store/collection'
|
import { useCollectionStore } from '../store/collection'
|
||||||
|
import { recordPageVisit } from '../api/visit.js'
|
||||||
import LongImageViewer from './LongImageViewer.vue'
|
import LongImageViewer from './LongImageViewer.vue'
|
||||||
import MediaPlayer from './MediaPlayer.vue'
|
import MediaPlayer from './MediaPlayer.vue'
|
||||||
import QianmenScene from './QianmenScene.vue'
|
import QianmenScene from './QianmenScene.vue'
|
||||||
|
|
@ -146,6 +147,12 @@ const collectedSeals = computed(() => {
|
||||||
|
|
||||||
// 组件挂载后初始化
|
// 组件挂载后初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 记录页面访问
|
||||||
|
recordPageVisit({
|
||||||
|
user_agent: navigator.userAgent,
|
||||||
|
page_name: 'home'
|
||||||
|
})
|
||||||
|
|
||||||
// 检查并初始化场景交互状态
|
// 检查并初始化场景交互状态
|
||||||
scenes.value.forEach((scene, index) => {
|
scenes.value.forEach((scene, index) => {
|
||||||
if (index > 0 && index < scenes.value.length) {
|
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