Compare commits
No commits in common. "6e0be666feee87a6c2695a8869c094c1aad5876a" and "1f42e5d7135ef7732b8993d81f5a88ffbb613ab2" have entirely different histories.
6e0be666fe
...
1f42e5d713
|
|
@ -1,275 +0,0 @@
|
||||||
# H5海报项目 API 文档
|
|
||||||
|
|
||||||
本文档描述了H5海报项目的后端API接口,供前端开发使用。
|
|
||||||
|
|
||||||
## 基础信息
|
|
||||||
|
|
||||||
- **Base URL**: `http://localhost:8080` (开发环境) / 生产环境使用相对路径
|
|
||||||
- **Content-Type**: `application/json`
|
|
||||||
- **超时时间**: 60秒
|
|
||||||
- **调试模式**: 通过配置文件中的 `debug` 字段控制,默认为 `true`
|
|
||||||
|
|
||||||
## UUID关联机制
|
|
||||||
|
|
||||||
本系统使用UUID来跟踪用户的完整操作流程:
|
|
||||||
|
|
||||||
1. **页面访问** (`/api/page-visit`): 用户首次访问页面时生成唯一的UUID
|
|
||||||
2. **对联生成** (`/api/couplets`): 使用页面访问的UUID关联海报生成操作
|
|
||||||
3. **用户信息提交** (`/api/user-info`): 使用相同的UUID关联用户信息提交
|
|
||||||
|
|
||||||
这种机制允许系统追踪从页面访问到最终用户提交的完整用户行为路径。
|
|
||||||
|
|
||||||
## 调试模式配置
|
|
||||||
|
|
||||||
系统支持通过配置文件控制Gin框架的运行模式:
|
|
||||||
|
|
||||||
- **debug: true** (默认): Gin运行在调试模式,输出详细的错误信息和日志
|
|
||||||
- **debug: false**: Gin运行在发布模式,性能更好,错误信息更简洁
|
|
||||||
|
|
||||||
建议开发环境使用 `true`,生产环境使用 `false`
|
|
||||||
|
|
||||||
## 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,
|
|
||||||
"uuid": "550e8400-e29b-41d4-a716-446655440000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应** (400 Bad Request)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Invalid request data"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应** (409 Conflict)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "该手机号码已经提交过用户信息,请勿重复提交"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应** (409 Conflict)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "该页面访问已经提交过用户信息,请勿重复提交"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**错误响应** (500 Internal Server Error)
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Database not available"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 使用说明
|
|
||||||
- 每次用户访问页面时调用此API
|
|
||||||
- 系统会自动记录用户的IP地址
|
|
||||||
- 如果数据库不可用,API会返回500错误
|
|
||||||
- 建议在页面加载完成后异步调用,不影响用户体验
|
|
||||||
- **重要**: 返回的UUID需要保存,后续的couplets和user-info接口需要使用这个UUID进行关联
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. 用户信息保存 - `/api/user-info`
|
|
||||||
|
|
||||||
保存用户填写的个人信息,用于活动参与或后续联系。
|
|
||||||
|
|
||||||
#### 请求信息
|
|
||||||
- **方法**: `POST`
|
|
||||||
- **路径**: `/api/user-info`
|
|
||||||
|
|
||||||
#### 请求参数
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 必需 | 描述 |
|
|
||||||
|--------|------|------|------|
|
|
||||||
| name | string | 是 | 用户姓名 |
|
|
||||||
| phone | string | 是 | 用户手机号码 |
|
|
||||||
| address | string | 是 | 用户地址 |
|
|
||||||
| msg | string | 是 | 用户留言或备注信息,最多200字 |
|
|
||||||
| page_visit_uuid | string | 是 | 页面访问UUID,用于关联页面访问记录 |
|
|
||||||
|
|
||||||
#### 请求示例
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "张三",
|
|
||||||
"phone": "13800138000",
|
|
||||||
"address": "北京市朝阳区xxx街道xxx号",
|
|
||||||
"msg": "希望能够获得精美礼品,谢谢!",
|
|
||||||
"page_visit_uuid": "550e8400-e29b-41d4-a716-446655440000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应信息
|
|
||||||
|
|
||||||
**成功响应** (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个字符,用于用户留言或备注
|
|
||||||
- 系统会自动记录创建时间
|
|
||||||
- 建议在前端进行基础的数据验证(如手机号格式)
|
|
||||||
- **重要**: 必须使用之前从`/api/page-visit`获得的UUID作为`page_visit_uuid`参数
|
|
||||||
- **防重复提交**: 系统会检查手机号和页面访问UUID的唯一性,如果发现重复提交会返回409错误
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. 对联海报生成 - `/api/couplets`
|
|
||||||
|
|
||||||
根据用户输入的两个汉字,使用AI生成对联并创建海报。
|
|
||||||
|
|
||||||
#### 请求信息
|
|
||||||
- **方法**: `POST`
|
|
||||||
- **路径**: `/api/couplets`
|
|
||||||
|
|
||||||
#### 请求参数
|
|
||||||
|
|
||||||
| 参数名 | 类型 | 必需 | 描述 |
|
|
||||||
|--------|------|------|------|
|
|
||||||
| title | string | 是 | 两个汉字,用于生成对联(如"新春") |
|
|
||||||
| page_visit_uuid | string | 是 | 页面访问UUID,用于关联页面访问记录 |
|
|
||||||
|
|
||||||
#### 请求示例
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"title": "新春",
|
|
||||||
"page_visit_uuid": "550e8400-e29b-41d4-a716-446655440000"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 响应信息
|
|
||||||
|
|
||||||
**成功响应** (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/page-visit`获得的UUID作为`page_visit_uuid`参数
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 错误处理
|
|
||||||
|
|
||||||
所有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
|
|
@ -1,56 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
166
api/request.js
|
|
@ -1,166 +0,0 @@
|
||||||
/**
|
|
||||||
* 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 {
|
|
||||||
// 创建包含状态码和响应数据的错误对象
|
|
||||||
const error = new Error(`HTTP ${response.statusCode}: ${response.errMsg || '请求失败'}`)
|
|
||||||
error.statusCode = response.statusCode
|
|
||||||
error.response = response
|
|
||||||
error.data = result.data || response.data
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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
|
|
||||||
}
|
|
||||||
22
api/user.js
|
|
@ -1,22 +0,0 @@
|
||||||
/**
|
|
||||||
* 用户信息相关 API
|
|
||||||
*/
|
|
||||||
import { post } from './request.js'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存用户信息
|
|
||||||
* @param {Object} data - 用户数据
|
|
||||||
* @param {string} data.name - 用户姓名
|
|
||||||
* @param {string} data.phone - 用户手机号码
|
|
||||||
* @param {string} data.address - 用户地址
|
|
||||||
* @param {string} data.msg - 用户留言
|
|
||||||
* @param {string} data.page_visit_uuid - 页面访问UUID
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
export const saveUserInfo = (data) => {
|
|
||||||
return post('/api/user-info', data)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
saveUserInfo
|
|
||||||
}
|
|
||||||
27
api/visit.js
|
|
@ -1,27 +0,0 @@
|
||||||
/**
|
|
||||||
* 页面访问相关 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
|
|
||||||
}
|
|
||||||
|
|
@ -122,25 +122,12 @@ const props = defineProps({
|
||||||
longfusi: false,
|
longfusi: false,
|
||||||
dongzhimen: false
|
dongzhimen: false
|
||||||
})
|
})
|
||||||
},
|
|
||||||
hasSubmitted: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['lottery', 'couplet', 'restart'])
|
const emit = defineEmits(['lottery', 'couplet', 'restart'])
|
||||||
|
|
||||||
const handleLottery = () => {
|
const handleLottery = () => {
|
||||||
// 如果已经提交过,直接提示
|
|
||||||
if (props.hasSubmitted) {
|
|
||||||
uni.showToast({
|
|
||||||
title: '您已经提交过信息了,请勿重复提交',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
emit('lottery')
|
emit('lottery')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -109,10 +109,10 @@ onMounted(() => {
|
||||||
transform: translateX(-50%) rotate(0deg);
|
transform: translateX(-50%) rotate(0deg);
|
||||||
}
|
}
|
||||||
25% {
|
25% {
|
||||||
transform: translateX(-50%) rotate(-6deg);
|
transform: translateX(-50%) rotate(-3deg);
|
||||||
}
|
}
|
||||||
75% {
|
75% {
|
||||||
transform: translateX(-50%) rotate(6deg);
|
transform: translateX(-50%) rotate(3deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,305 +0,0 @@
|
||||||
<script setup>
|
|
||||||
import { ref, watch, nextTick } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
visible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
hasSubmitted: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['close', 'submit'])
|
|
||||||
|
|
||||||
// 表单数据
|
|
||||||
const formData = ref({
|
|
||||||
name: '',
|
|
||||||
phone: '',
|
|
||||||
address: '',
|
|
||||||
msg: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听 visible 变化,打开时重置表单
|
|
||||||
watch(() => props.visible, (newVal) => {
|
|
||||||
if (newVal) {
|
|
||||||
formData.value = {
|
|
||||||
name: '',
|
|
||||||
phone: '',
|
|
||||||
address: '',
|
|
||||||
msg: ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 关闭页面
|
|
||||||
const handleClose = () => {
|
|
||||||
emit('close')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示提示信息
|
|
||||||
const showToast = (title) => {
|
|
||||||
nextTick(() => {
|
|
||||||
uni.showToast({
|
|
||||||
title,
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const handleSubmit = () => {
|
|
||||||
// 如果已经提交过,直接提示
|
|
||||||
if (props.hasSubmitted) {
|
|
||||||
showToast('您已经提交过信息了,请勿重复提交')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('提交表单', formData.value)
|
|
||||||
|
|
||||||
// 验证表单
|
|
||||||
console.log('表单数据:', formData.value)
|
|
||||||
if (!formData.value.name || !formData.value.phone || !formData.value.address || !formData.value.msg) {
|
|
||||||
showToast('请填写完整信息')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证手机号
|
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/
|
|
||||||
const phone = formData.value.phone.trim()
|
|
||||||
console.log('手机号验证:', phone, '长度:', phone.length, '匹配结果:', phoneRegex.test(phone))
|
|
||||||
if (!phoneRegex.test(phone)) {
|
|
||||||
showToast('请输入正确的手机号')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证留言
|
|
||||||
if (!formData.value.msg || formData.value.msg.trim() === '') {
|
|
||||||
showToast('请输入留言')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证留言长度
|
|
||||||
const msgLength = formData.value.msg.length
|
|
||||||
if (msgLength > 200) {
|
|
||||||
showToast('留言最多200个汉字')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交前清理数据
|
|
||||||
const submitData = {
|
|
||||||
name: formData.value.name.trim(),
|
|
||||||
phone: formData.value.phone.trim(),
|
|
||||||
address: formData.value.address.trim(),
|
|
||||||
msg: formData.value.msg.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
emit('submit', submitData)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="info-page" v-if="visible">
|
|
||||||
<!-- 背景图片 -->
|
|
||||||
<div class="bg-container">
|
|
||||||
<img src="/static/info/info_bg.jpg" class="bg-image" mode="aspectFill" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 返回按钮 -->
|
|
||||||
<div class="back-btn" @click="handleClose">
|
|
||||||
<img src="/static/images/btn_back.png" class="back-icon" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 标题 -->
|
|
||||||
<div class="title-container">
|
|
||||||
<img src="/static/info/info_title.png" class="title-image" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 信息提示 -->
|
|
||||||
<div class="tips-container">
|
|
||||||
<img src="/static/info/info_tips.png" class="tips-image" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 表单区域 -->
|
|
||||||
<div class="form-container">
|
|
||||||
<div class="form-item">
|
|
||||||
<input
|
|
||||||
v-model="formData.name"
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入您的姓名"
|
|
||||||
class="form-input"
|
|
||||||
maxlength="30"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-item">
|
|
||||||
<input
|
|
||||||
v-model="formData.phone"
|
|
||||||
type="tel"
|
|
||||||
placeholder="请输入您的联系电话"
|
|
||||||
class="form-input"
|
|
||||||
maxlength="11"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-item">
|
|
||||||
<input
|
|
||||||
v-model="formData.address"
|
|
||||||
type="text"
|
|
||||||
placeholder="请输入您的邮寄地址"
|
|
||||||
class="form-input"
|
|
||||||
maxlength="100"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-item">
|
|
||||||
<textarea
|
|
||||||
v-model="formData.msg"
|
|
||||||
placeholder="请输入您的留言"
|
|
||||||
class="form-textarea"
|
|
||||||
maxlength="200"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
<span class="char-count">{{ formData.msg.length }}/200</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 提交按钮 -->
|
|
||||||
<div class="submit-container">
|
|
||||||
<img
|
|
||||||
src="/static/images/btn_submit.png"
|
|
||||||
class="submit-btn-image"
|
|
||||||
@click="handleSubmit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.info-page {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 100;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 背景图片 */
|
|
||||||
.bg-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-image {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 返回按钮 */
|
|
||||||
.back-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 30rpx;
|
|
||||||
left: 30rpx;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.back-icon {
|
|
||||||
width: 68rpx;
|
|
||||||
height: 68rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 标题 */
|
|
||||||
.title-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: 100rpx;
|
|
||||||
margin-bottom: 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title-image {
|
|
||||||
width: 544rpx;
|
|
||||||
height: 213rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 信息提示 */
|
|
||||||
.tips-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tips-image {
|
|
||||||
width: 697rpx;
|
|
||||||
height: 258rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 表单区域 */
|
|
||||||
.form-container {
|
|
||||||
padding: 0 50rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-item {
|
|
||||||
margin-bottom: 16rpx;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input,
|
|
||||||
.form-textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 20rpx;
|
|
||||||
border: none;
|
|
||||||
border-radius: 10rpx;
|
|
||||||
font-size: 28rpx;
|
|
||||||
background-color: #fff;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input {
|
|
||||||
height: 70rpx;
|
|
||||||
padding: 16rpx 20rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-textarea {
|
|
||||||
height: 150rpx;
|
|
||||||
padding: 16rpx 20rpx;
|
|
||||||
resize: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.char-count {
|
|
||||||
position: absolute;
|
|
||||||
right: 20rpx;
|
|
||||||
bottom: 20rpx;
|
|
||||||
color: #999;
|
|
||||||
font-size: 24rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 提交按钮 */
|
|
||||||
.submit-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 30rpx;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn-image {
|
|
||||||
width: 306rpx;
|
|
||||||
height: 97rpx;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn-image:active {
|
|
||||||
transform: scale(0.98);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -175,7 +175,7 @@ onUnmounted(() => {
|
||||||
<!-- 音乐控制按钮 -->
|
<!-- 音乐控制按钮 -->
|
||||||
<div class="music-control" @click="toggleMusic">
|
<div class="music-control" @click="toggleMusic">
|
||||||
<img
|
<img
|
||||||
:src="isMusicPlaying ? '/static/images/icon_music2.png' : '/static/images/icon_music1.png'"
|
src="/static/images/icon_music.png"
|
||||||
alt="音乐控制"
|
alt="音乐控制"
|
||||||
class="music-icon"
|
class="music-icon"
|
||||||
:class="{ 'playing': isMusicPlaying }"
|
:class="{ 'playing': isMusicPlaying }"
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@
|
||||||
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 { saveUserInfo } from '../api/user.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'
|
||||||
|
|
@ -12,7 +10,6 @@ import LongfusiScene from './LongfusiScene.vue'
|
||||||
import WangfujingScene from './WangfujingScene.vue'
|
import WangfujingScene from './WangfujingScene.vue'
|
||||||
import ChongwenScene from './ChongwenScene.vue'
|
import ChongwenScene from './ChongwenScene.vue'
|
||||||
import EndPage from './EndPage.vue'
|
import EndPage from './EndPage.vue'
|
||||||
import LotteryFormModal from './LotteryFormModal.vue'
|
|
||||||
|
|
||||||
const sceneStore = useSceneStore()
|
const sceneStore = useSceneStore()
|
||||||
const collectionStore = useCollectionStore()
|
const collectionStore = useCollectionStore()
|
||||||
|
|
@ -43,11 +40,12 @@ const showCoupletDisplay = ref(false)
|
||||||
const coupletKeyword = ref('')
|
const coupletKeyword = ref('')
|
||||||
// 生成的春联
|
// 生成的春联
|
||||||
const generatedCouplet = ref(null)
|
const generatedCouplet = ref(null)
|
||||||
// 页面访问UUID
|
// 抽奖表单数据
|
||||||
const pageVisitUuid = ref('')
|
const lotteryForm = ref({
|
||||||
// 是否已提交用户信息
|
name: '',
|
||||||
const hasSubmittedUserInfo = ref(false)
|
phone: '',
|
||||||
|
address: ''
|
||||||
|
})
|
||||||
|
|
||||||
// 推荐关键词
|
// 推荐关键词
|
||||||
const recommendedKeywords = ref(['吉祥', '如意', '平安', '健康', '幸福', '快乐', '富贵', '安康'])
|
const recommendedKeywords = ref(['吉祥', '如意', '平安', '健康', '幸福', '快乐', '富贵', '安康'])
|
||||||
|
|
@ -64,6 +62,7 @@ const scenes = ref([
|
||||||
id: 'dongzhimen',
|
id: 'dongzhimen',
|
||||||
name: '东直门商圈',
|
name: '东直门商圈',
|
||||||
description: '京城东部的交通枢纽和商业中心',
|
description: '京城东部的交通枢纽和商业中心',
|
||||||
|
image: 'https://picsum.photos/id/1019/750/1600',
|
||||||
interactionTip: '移动烤鸭,领取团圆福筷',
|
interactionTip: '移动烤鸭,领取团圆福筷',
|
||||||
collectedItem: '团圆福筷'
|
collectedItem: '团圆福筷'
|
||||||
},
|
},
|
||||||
|
|
@ -71,6 +70,7 @@ const scenes = ref([
|
||||||
id: 'longfusi',
|
id: 'longfusi',
|
||||||
name: '隆福寺商圈',
|
name: '隆福寺商圈',
|
||||||
description: '传统文化与现代艺术融合的潮流地标',
|
description: '传统文化与现代艺术融合的潮流地标',
|
||||||
|
image: 'https://picsum.photos/id/1018/750/1600',
|
||||||
interactionTip: '点击文创物品,点亮文化福灯',
|
interactionTip: '点击文创物品,点亮文化福灯',
|
||||||
collectedItem: '文化福灯'
|
collectedItem: '文化福灯'
|
||||||
},
|
},
|
||||||
|
|
@ -78,6 +78,7 @@ const scenes = ref([
|
||||||
id: 'wangfujing',
|
id: 'wangfujing',
|
||||||
name: '王府井商圈',
|
name: '王府井商圈',
|
||||||
description: '北京最繁华的商业中心之一',
|
description: '北京最繁华的商业中心之一',
|
||||||
|
image: 'https://picsum.photos/id/1018/750/1600',
|
||||||
interactionTip: '双指放大,收集金袋福卡',
|
interactionTip: '双指放大,收集金袋福卡',
|
||||||
collectedItem: '金袋福卡'
|
collectedItem: '金袋福卡'
|
||||||
},
|
},
|
||||||
|
|
@ -85,6 +86,7 @@ const scenes = ref([
|
||||||
id: 'chongwen',
|
id: 'chongwen',
|
||||||
name: '崇文门商圈',
|
name: '崇文门商圈',
|
||||||
description: '融合传统文化与现代商业的活力区域',
|
description: '融合传统文化与现代商业的活力区域',
|
||||||
|
image: 'https://picsum.photos/id/1016/750/1600',
|
||||||
interactionTip: '滑动探索商圈,收集国潮福字',
|
interactionTip: '滑动探索商圈,收集国潮福字',
|
||||||
collectedItem: '国潮福字'
|
collectedItem: '国潮福字'
|
||||||
},
|
},
|
||||||
|
|
@ -92,6 +94,8 @@ const scenes = ref([
|
||||||
id: 'qianmen',
|
id: 'qianmen',
|
||||||
name: '前门商圈',
|
name: '前门商圈',
|
||||||
description: '北京最具历史文化底蕴的商圈之一',
|
description: '北京最具历史文化底蕴的商圈之一',
|
||||||
|
image: 'https://picsum.photos/id/1015/750/1600',
|
||||||
|
thumbnail: 'https://picsum.photos/id/1015/200/100',
|
||||||
interactionTip: '点击京韵大鼓,收集非遗福印',
|
interactionTip: '点击京韵大鼓,收集非遗福印',
|
||||||
collectedItem: '非遗福印'
|
collectedItem: '非遗福印'
|
||||||
},
|
},
|
||||||
|
|
@ -142,19 +146,6 @@ const collectedSeals = computed(() => {
|
||||||
|
|
||||||
// 组件挂载后初始化
|
// 组件挂载后初始化
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 记录页面访问
|
|
||||||
recordPageVisit({
|
|
||||||
user_agent: navigator.userAgent,
|
|
||||||
page_name: 'home'
|
|
||||||
}).then(res => {
|
|
||||||
if (res && res.uuid) {
|
|
||||||
pageVisitUuid.value = res.uuid
|
|
||||||
console.log('页面访问UUID:', res.uuid)
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.log('页面访问记录失败:', err)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 检查并初始化场景交互状态
|
// 检查并初始化场景交互状态
|
||||||
scenes.value.forEach((scene, index) => {
|
scenes.value.forEach((scene, index) => {
|
||||||
if (index > 0 && index < scenes.value.length) {
|
if (index > 0 && index < scenes.value.length) {
|
||||||
|
|
@ -346,80 +337,49 @@ const openLotteryForm = () => {
|
||||||
showLotteryForm.value = true
|
showLotteryForm.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭抽奖留资弹窗
|
|
||||||
const closeLotteryForm = () => {
|
|
||||||
showLotteryForm.value = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交抽奖表单
|
// 提交抽奖表单
|
||||||
const submitLotteryForm = async (formData) => {
|
const submitLotteryForm = () => {
|
||||||
// 检查是否已经提交过
|
// 验证表单
|
||||||
if (hasSubmittedUserInfo.value) {
|
if (!lotteryForm.value.name || !lotteryForm.value.phone || !lotteryForm.value.address) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '您已经提交过信息了,请勿重复提交',
|
title: '请填写完整信息',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有UUID
|
// 验证手机号
|
||||||
if (!pageVisitUuid.value) {
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
|
if (!phoneRegex.test(lotteryForm.value.phone)) {
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '系统错误,请刷新页面重试',
|
title: '请输入正确的手机号',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 模拟提交
|
||||||
uni.showLoading({
|
uni.showLoading({
|
||||||
title: '提交中...',
|
title: '提交中...',
|
||||||
mask: true
|
mask: true
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
setTimeout(() => {
|
||||||
// 调用用户信息保存接口
|
|
||||||
await saveUserInfo({
|
|
||||||
name: formData.name,
|
|
||||||
phone: formData.phone,
|
|
||||||
address: formData.address,
|
|
||||||
msg: formData.msg,
|
|
||||||
page_visit_uuid: pageVisitUuid.value
|
|
||||||
})
|
|
||||||
|
|
||||||
// 标记为已提交
|
|
||||||
hasSubmittedUserInfo.value = true
|
|
||||||
|
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
uni.showToast({
|
uni.showToast({
|
||||||
title: '提交成功!我们将在活动结束后通知您抽奖结果',
|
title: '报名成功!我们将在活动结束后通知您抽奖结果',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
showLotteryForm.value = false
|
showLotteryForm.value = false
|
||||||
} catch (error) {
|
// 重置表单
|
||||||
uni.hideLoading()
|
lotteryForm.value = {
|
||||||
console.error('提交用户信息失败:', error)
|
name: '',
|
||||||
|
phone: '',
|
||||||
// 直接展示后端返回的错误信息
|
address: ''
|
||||||
let errorMessage = '提交失败,请稍后重试'
|
|
||||||
|
|
||||||
// 检查是否有后端返回的错误信息
|
|
||||||
if (error.data && error.data.error) {
|
|
||||||
errorMessage = error.data.error
|
|
||||||
} else if (error.response && error.response.data && error.response.data.error) {
|
|
||||||
errorMessage = error.response.data.error
|
|
||||||
} else if (error.message) {
|
|
||||||
// 如果是网络错误或其他错误,显示通用提示
|
|
||||||
errorMessage = '提交失败,请稍后重试'
|
|
||||||
}
|
|
||||||
|
|
||||||
uni.showToast({
|
|
||||||
title: errorMessage,
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
}, 1500)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开AI春联生成弹窗
|
// 打开AI春联生成弹窗
|
||||||
|
|
@ -521,10 +481,8 @@ onUnmounted(() => {
|
||||||
scroll-y="true"
|
scroll-y="true"
|
||||||
scroll-with-animation="true"
|
scroll-with-animation="true"
|
||||||
enhanced="true"
|
enhanced="true"
|
||||||
:refresher-enabled="false"
|
refresher-enabled="false"
|
||||||
:refresher-triggered="false"
|
|
||||||
show-scrollbar="false"
|
show-scrollbar="false"
|
||||||
:bounce="false"
|
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
:scroll-into-view="homeSectionId"
|
:scroll-into-view="homeSectionId"
|
||||||
>
|
>
|
||||||
|
|
@ -536,7 +494,6 @@ onUnmounted(() => {
|
||||||
:collected-count="collectedItems.length"
|
:collected-count="collectedItems.length"
|
||||||
:total-count="scenes.length - 1"
|
:total-count="scenes.length - 1"
|
||||||
:collected-seals="collectedSeals"
|
:collected-seals="collectedSeals"
|
||||||
:has-submitted="hasSubmittedUserInfo"
|
|
||||||
@lottery="openLotteryForm"
|
@lottery="openLotteryForm"
|
||||||
@couplet="openAICoupletForm"
|
@couplet="openAICoupletForm"
|
||||||
@restart="scrollToTop"
|
@restart="scrollToTop"
|
||||||
|
|
@ -616,12 +573,33 @@ onUnmounted(() => {
|
||||||
</div> -->
|
</div> -->
|
||||||
|
|
||||||
<!-- 抽奖留资弹窗 -->
|
<!-- 抽奖留资弹窗 -->
|
||||||
<LotteryFormModal
|
<div class="form-modal" v-if="showLotteryForm">
|
||||||
:visible="showLotteryForm"
|
<div class="modal-overlay" @click="showLotteryForm = false"></div>
|
||||||
:has-submitted="hasSubmittedUserInfo"
|
<div class="modal-content">
|
||||||
@close="closeLotteryForm"
|
<div class="modal-header">
|
||||||
@submit="submitLotteryForm"
|
<h3>参与抽奖</h3>
|
||||||
/>
|
<button class="close-btn" @click="showLotteryForm = false">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="modal-description">请填写以下信息,参与线下抽奖活动</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>姓名</label>
|
||||||
|
<input v-model="lotteryForm.name" type="text" placeholder="请输入您的姓名" class="form-input" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>联系电话</label>
|
||||||
|
<input v-model="lotteryForm.phone" type="tel" placeholder="请输入您的手机号" class="form-input" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label>邮寄地址</label>
|
||||||
|
<textarea v-model="lotteryForm.address" placeholder="请输入您的邮寄地址" class="form-textarea" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button class="submit-btn" @click="submitLotteryForm">提交报名</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- AI春联生成弹窗 -->
|
<!-- AI春联生成弹窗 -->
|
||||||
<div class="form-modal" v-if="showAICoupletForm">
|
<div class="form-modal" v-if="showAICoupletForm">
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 239 KiB |
|
Before Width: | Height: | Size: 447 KiB After Width: | Height: | Size: 448 KiB |
|
Before Width: | Height: | Size: 535 KiB After Width: | Height: | Size: 612 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 126 KiB |