1、增加页面访问api
2、背景图片更换
3、福字摇晃幅度加大
This commit is contained in:
Wenzhe 2026-02-02 11:29:25 +08:00
parent 1f42e5d713
commit 2995cdb3e2
9 changed files with 485 additions and 2 deletions

232
api/API_DOCUMENTATION.md Normal file
View File

@ -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)
}
}
```

56
api/api.js Normal file
View File

@ -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)
}
}

161
api/request.js Normal file
View File

@ -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
}

27
api/visit.js Normal file
View File

@ -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
}

View File

@ -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);
} }
} }

View File

@ -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