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(() => { /> -
- - -
+ -
- - -
+ @@ -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