diff --git a/api/api.js b/api/api.js
index cd1127d..0363317 100644
--- a/api/api.js
+++ b/api/api.js
@@ -1,56 +1,54 @@
-import axios from 'axios'
+/**
+ * API 统一入口
+ * 所有 API 函数都从此文件导入
+ */
+import { post } from './request.js'
-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 || '生成海报失败')
- }
+/**
+ * 保存用户信息
+ * @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 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 || '获取海报失败')
- }
+/**
+ * 生成对联海报
+ * @param {Object} data - 请求数据
+ * @param {string} data.title - 两个汉字,用于生成对联(如"新春")
+ * @param {string} data.page_visit_uuid - 页面访问UUID,用于关联页面访问记录
+ * @returns {Promise} - 返回包含 share_url, poster_id, image_url 的 Promise
+ */
+export const generateCoupletPoster = (data) => {
+ return post('/api/couplets', data)
}
-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 || '生成对联海报失败')
+/**
+ * 记录页面访问
+ * @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 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 default {
+ saveUserInfo,
+ generateCoupletPoster,
+ recordPageVisit
}
-
-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)
- }
-}
\ No newline at end of file
diff --git a/api/user.js b/api/user.js
deleted file mode 100644
index 9403da4..0000000
--- a/api/user.js
+++ /dev/null
@@ -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
-}
diff --git a/api/visit.js b/api/visit.js
deleted file mode 100644
index 95cb783..0000000
--- a/api/visit.js
+++ /dev/null
@@ -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
-}
diff --git a/components/AICoupletForm.vue b/components/AICoupletForm.vue
new file mode 100644
index 0000000..a5a7c7e
--- /dev/null
+++ b/components/AICoupletForm.vue
@@ -0,0 +1,292 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/CoupletDisplay.vue b/components/CoupletDisplay.vue
new file mode 100644
index 0000000..f834caa
--- /dev/null
+++ b/components/CoupletDisplay.vue
@@ -0,0 +1,115 @@
+
+
+
+
+
+
+
+
+
![春联海报]()
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/components/EndPage.vue b/components/EndPage.vue
index f7e61dd..bc909dc 100644
--- a/components/EndPage.vue
+++ b/components/EndPage.vue
@@ -126,10 +126,14 @@ const props = defineProps({
hasSubmitted: {
type: Boolean,
default: false
+ },
+ generatedCouplet: {
+ type: Object,
+ default: () => null
}
})
-const emit = defineEmits(['lottery', 'couplet', 'restart'])
+const emit = defineEmits(['lottery', 'couplet', 'restart', 'showCouplet'])
const handleLottery = () => {
// 如果已经提交过,直接提示
@@ -145,6 +149,11 @@ const handleLottery = () => {
}
const handleCouplet = () => {
+ // 如果已经生成过海报,直接显示海报
+ if (props.generatedCouplet && props.generatedCouplet.image_url) {
+ emit('showCouplet')
+ return
+ }
emit('couplet')
}
diff --git a/components/SinglePageContainer.vue b/components/SinglePageContainer.vue
index 7cabcbf..db912f7 100644
--- a/components/SinglePageContainer.vue
+++ b/components/SinglePageContainer.vue
@@ -2,8 +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 { saveUserInfo } from '../api/user.js'
+import { recordPageVisit, saveUserInfo, generateCoupletPoster } from '../api/api.js'
import LongImageViewer from './LongImageViewer.vue'
import MediaPlayer from './MediaPlayer.vue'
import QianmenScene from './QianmenScene.vue'
@@ -13,6 +12,8 @@ import WangfujingScene from './WangfujingScene.vue'
import ChongwenScene from './ChongwenScene.vue'
import EndPage from './EndPage.vue'
import LotteryFormModal from './LotteryFormModal.vue'
+import AICoupletForm from './AICoupletForm.vue'
+import CoupletDisplay from './CoupletDisplay.vue'
const sceneStore = useSceneStore()
const collectionStore = useCollectionStore()
@@ -427,54 +428,46 @@ const openAICoupletForm = () => {
showAICoupletForm.value = true
}
-// 选择推荐关键词
-const selectRecommendedKeyword = (word) => {
- coupletKeyword.value = word
-}
-
-// 生成春联
-const generateCouplet = () => {
- if (!coupletKeyword.value || coupletKeyword.value.length < 2) {
- uni.showToast({
- title: '请输入两个字的关键词',
- icon: 'none',
- duration: 2000
- })
- return
- }
-
+// 处理生成春联
+const handleGenerateCouplet = async (keyword) => {
uni.showLoading({
title: '生成中...',
mask: true
})
- // 模拟AI生成春联
- setTimeout(() => {
+ try {
+ // 调用后端 API 生成春联海报
+ const response = await generateCoupletPoster({
+ title: keyword,
+ page_visit_uuid: pageVisitUuid.value
+ })
+
uni.hideLoading()
- // 生成春联(这里使用预设模板)
- const coupletTemplates = [
- {
- top: `${coupletKeyword.value.charAt(0)}气盈门添福寿`,
- bottom: `${coupletKeyword.value.charAt(1)}光满院报平安`,
- 横批: '新春大吉'
- },
- {
- top: `${coupletKeyword.value.charAt(0)}祥如意年年好`,
- bottom: `${coupletKeyword.value.charAt(1)}事顺心步步高`,
- 横批: '万事如意'
- },
- {
- top: `${coupletKeyword.value.charAt(0)}星高照家昌盛`,
- bottom: `${coupletKeyword.value.charAt(1)}运亨通福满堂`,
- 横批: '吉星高照'
- }
- ]
+ // 构建春联数据 - 使用 API 返回的参数
+ generatedCouplet.value = {
+ share_url: response.share_url,
+ image_url: response.image_url
+ }
- generatedCouplet.value = coupletTemplates[Math.floor(Math.random() * coupletTemplates.length)]
showCoupletDisplay.value = true
showAICoupletForm.value = false
- }, 1500)
+ } catch (error) {
+ uni.hideLoading()
+
+ // 显示错误信息
+ uni.showToast({
+ title: error.message || '生成春联失败,请稍后重试',
+ icon: 'none',
+ duration: 2000
+ })
+ }
+}
+
+// 处理分享春联
+const handleShareCouplet = () => {
+ // 分享逻辑可以在这里扩展
+ console.log('分享春联:', generatedCouplet.value)
}
// 返回顶部
@@ -537,8 +530,10 @@ onUnmounted(() => {
:total-count="scenes.length - 1"
:collected-seals="collectedSeals"
:has-submitted="hasSubmittedUserInfo"
+ :generated-couplet="generatedCouplet"
@lottery="openLotteryForm"
@couplet="openAICoupletForm"
+ @show-couplet="showCoupletDisplay = true"
@restart="scrollToTop"
/>
@@ -624,55 +619,19 @@ onUnmounted(() => {
/>
-
+
-
-
-
-
-
-
-
{{ generatedCouplet.top }}
-
{{ generatedCouplet.bottom }}
-
{{ generatedCouplet.横批 }}
-
-
-
您收集的福印:
-
- �� {{ item }}
-
-
-
-
-
-
-
-
-
+
@@ -794,346 +753,6 @@ onUnmounted(() => {
background-color: #f5f5f5;
}
-.scene-section.active {
- /* 当前活动场景样式 */
-}
-
-.scene-content {
- width: 100%;
- max-width: 640px;
- padding: 20px;
- box-sizing: border-box;
-}
-
-.scene-title {
- font-size: 28px;
- color: #333;
- margin-bottom: 10px;
- text-align: center;
-}
-
-.scene-description {
- font-size: 16px;
- color: #666;
- margin-bottom: 20px;
- text-align: center;
-}
-
-.long-image-container {
- position: relative;
- width: 100%;
- height: 50vh;
- margin-bottom: 20px;
- background-color: #000;
- border-radius: 10px;
- overflow: hidden;
-}
-
-.interaction-tip {
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background-color: rgba(0, 0, 0, 0.7);
- color: #fff;
- padding: 15px 25px;
- border-radius: 10px;
- z-index: 10;
- animation: pulse 1.5s infinite;
-}
-
-@keyframes pulse {
- 0% {
- transform: translate(-50%, -50%) scale(1);
- }
- 50% {
- transform: translate(-50%, -50%) scale(1.1);
- }
- 100% {
- transform: translate(-50%, -50%) scale(1);
- }
-}
-
-.tip-icon {
- font-size: 36px;
- margin-bottom: 5px;
-}
-
-.seal-collect-tip {
- position: absolute;
- top: 20px;
- right: 20px;
- display: flex;
- align-items: center;
- background-color: rgba(255, 107, 53, 0.9);
- color: #fff;
- padding: 8px 15px;
- border-radius: 20px;
- font-size: 14px;
- z-index: 10;
-}
-
-.seal-icon {
- font-size: 24px;
- margin-right: 5px;
-}
-
-.media-section {
- margin-top: 20px;
-}
-
-/* 弹窗样式 */
-.form-modal,
-.couplet-display-modal {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 1000;
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.modal-overlay {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background-color: rgba(0, 0, 0, 0.7);
-}
-
-.modal-content {
- position: relative;
- background-color: white;
- border-radius: 15px;
- padding: 20px;
- width: 90%;
- max-width: 500px;
- max-height: 80vh;
- overflow-y: auto;
- z-index: 1001;
-}
-
-.close-btn {
- position: absolute;
- top: 10px;
- right: 15px;
- background: none;
- border: none;
- font-size: 24px;
- cursor: pointer;
- color: #666;
-}
-
-.modal-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- border-bottom: 1px solid #eee;
- padding-bottom: 10px;
-}
-
-.modal-header h3 {
- margin: 0;
- font-size: 20px;
- color: #333;
-}
-
-.modal-body {
- padding-bottom: 10px;
-}
-
-.modal-description {
- font-size: 16px;
- color: #666;
- margin-bottom: 20px;
- text-align: center;
-}
-
-.form-group {
- margin-bottom: 20px;
-}
-
-.form-input,
-.keyword-input,
-.form-textarea {
- width: 100%;
- padding: 12px;
- border: 1px solid #ddd;
- border-radius: 8px;
- font-size: 16px;
- box-sizing: border-box;
-}
-
-.form-textarea {
- resize: vertical;
- min-height: 80px;
-}
-
-.recommended-keywords {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- margin-bottom: 20px;
- justify-content: center;
-}
-
-.keyword-tag {
- padding: 8px 15px;
- background-color: #f0f0f0;
- border-radius: 20px;
- font-size: 14px;
- cursor: pointer;
- transition: all 0.3s ease;
-}
-
-.keyword-tag:hover {
- background-color: #e0e0e0;
-}
-
-.form-actions {
- text-align: center;
-}
-
-.submit-btn {
- padding: 12px 30px;
- font-size: 16px;
- background-color: #FF6B35;
- color: white;
- border: none;
- border-radius: 25px;
- cursor: pointer;
- transition: all 0.3s ease;
-}
-
-.submit-btn:hover:not(:disabled) {
- background-color: #E65A2B;
- transform: translateY(-2px);
-}
-
-.submit-btn:disabled {
- background-color: #ccc;
- cursor: not-allowed;
-}
-
-.modal-video {
- width: 100%;
- max-height: 70vh;
- border-radius: 10px;
-}
-
-/* 春联展示样式 */
-.couplet-content {
- max-width: 600px;
-}
-
-.couplet-display {
- text-align: center;
- margin-bottom: 30px;
- padding: 20px;
- background-color: #f9f9f9;
- border-radius: 15px;
-}
-
-.couplet-item {
- font-family: 'SimSun', 'STSong', serif;
- font-size: 24px;
- line-height: 1.8;
- margin: 10px 0;
-}
-
-.top-couplet {
- transform: rotate(90deg);
- transform-origin: right center;
- display: inline-block;
- margin-right: 40px;
-}
-
-.bottom-couplet {
- transform: rotate(-90deg);
- transform-origin: left center;
- display: inline-block;
- margin-left: 40px;
-}
-
-.横批 {
- font-size: 28px;
- font-weight: bold;
- color: #FF6B35;
- margin-top: 20px;
-}
-
-.collected-items-section {
- margin-top: 20px;
- padding-top: 20px;
- border-top: 1px solid #eee;
-}
-
-.items-title {
- font-size: 16px;
- color: #333;
- margin-bottom: 15px;
-}
-
-.collected-items {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- justify-content: center;
-}
-
-.item-tag {
- padding: 8px 15px;
- background-color: #fff3e0;
- border: 1px solid #ffb74d;
- border-radius: 20px;
- font-size: 14px;
- color: #e65100;
-}
-
-.couplet-actions {
- display: flex;
- gap: 15px;
- justify-content: center;
- margin-top: 20px;
-}
-
-.share-btn {
- padding: 10px 20px;
- background-color: #2196F3;
- color: white;
- border: none;
- border-radius: 20px;
- cursor: pointer;
- transition: all 0.3s ease;
-}
-
-.share-btn:hover {
- background-color: #1976D2;
-}
-
-.close-couplet-btn {
- padding: 10px 20px;
- background-color: #9E9E9E;
- color: white;
- border: none;
- border-radius: 20px;
- cursor: pointer;
- transition: all 0.3s ease;
-}
-
-.close-couplet-btn:hover {
- background-color: #757575;
-}
-
/* 首页样式 */
.home-section {
position: relative;
diff --git a/index.html b/index.html
index b5d330d..1fab2cc 100644
--- a/index.html
+++ b/index.html
@@ -2,6 +2,7 @@
+
+
+
\ No newline at end of file
diff --git a/static/info/couplet_info_box.png b/static/info/couplet_info_box.png
new file mode 100644
index 0000000..ac0f518
Binary files /dev/null and b/static/info/couplet_info_box.png differ