1、移除页面加载favicon
2、合并api请求方法
3、完成抽奖留资于ai春联生成功能,都采用组件方式进行调用
4、完成当次留资后,不能进行第二次留资
5、生成海报后,点击ai春联直接展示已经生成的海报
6、移除一些无效的样式代码
This commit is contained in:
Wenzhe 2026-02-02 21:57:32 +08:00
parent 6e0be666fe
commit 03d0e80d21
10 changed files with 652 additions and 524 deletions

View File

@ -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
}
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 || '保存用户信息失败')
}
return post('/api/page-visit', defaultData)
}
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)
}
export default {
saveUserInfo,
generateCoupletPoster,
recordPageVisit
}

View File

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

View File

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

View File

@ -0,0 +1,292 @@
<template>
<div class="form-modal" v-if="visible">
<div class="modal-overlay"></div>
<div class="modal-content">
<div class="modal-body">
<div class="couplet-form">
<div class="form-row">
<div class="input-group">
<label class="label">上联首字</label>
<input
v-model="topKeyword"
type="text"
maxlength="1"
class="underline-input"
@input="validateCharacter('top')"
/>
</div>
<div class="input-group">
<label class="label">下联首字</label>
<input
v-model="bottomKeyword"
type="text"
maxlength="1"
class="underline-input"
@input="validateCharacter('bottom')"
/>
</div>
</div>
<div class="form-actions">
<img
src="/static/images/btn_submit.png"
class="submit-btn-image"
@click="generateCouplet"
/>
</div>
</div>
</div>
</div>
<div class="close-button-area">
<img
src="/static/images/btn_close.png"
class="close-button-image"
@click="$emit('close')"
/>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
visible: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['close', 'generate'])
const topKeyword = ref('')
const bottomKeyword = ref('')
//
const isChineseCharacter = (char) => {
return /^[\u4e00-\u9fa5]$/.test(char)
}
//
const validateCharacter = (type) => {
if (type === 'top' && topKeyword.value) {
if (!isChineseCharacter(topKeyword.value)) {
topKeyword.value = ''
uni.showToast({
title: '请输入汉字',
icon: 'none',
duration: 2000
})
}
}
if (type === 'bottom' && bottomKeyword.value) {
if (!isChineseCharacter(bottomKeyword.value)) {
bottomKeyword.value = ''
uni.showToast({
title: '请输入汉字',
icon: 'none',
duration: 2000
})
}
}
}
//
const isFormValid = computed(() => {
return topKeyword.value && bottomKeyword.value
})
//
const generateCouplet = () => {
if (!isFormValid.value) {
uni.showToast({
title: '请输入完整的上下联首字',
icon: 'none',
duration: 2000
})
return
}
emit('generate', topKeyword.value + bottomKeyword.value)
topKeyword.value = ''
bottomKeyword.value = ''
}
</script>
<style scoped>
.form-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
}
.modal-content {
position: relative;
z-index: 99;
width: 697rpx;
height: 417rpx;
background-image: url('/static/info/couplet_info_box.png');
background-size: 100% 100%;
background-repeat: no-repeat;
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 30rpx;
}
.modal-header h3 {
margin: 0;
font-size: 36rpx;
color: #333;
}
.close-btn {
background: none;
border: none;
font-size: 48rpx;
cursor: pointer;
color: #666;
}
.modal-body {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 0 55rpx;
}
.couplet-form {
width: 100%;
margin-top: 150rpx;
text-align: center;
}
.form-title {
font-size: 48rpx;
color: #fff;
margin-bottom: 80rpx;
font-weight: bold;
text-shadow: 2rpx 2rpx 4rpx rgba(0, 0, 0, 0.3);
}
.form-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 60rpx;
flex-wrap: wrap;
}
.input-group {
width: 45%;
display: flex;
flex-direction: row;
align-items: flex-end;
gap: 10rpx;
}
.label {
color: white;
font-size: 32rpx;
text-shadow: 1rpx 1rpx 2rpx rgba(0, 0, 0, 0.3);
align-self: flex-end;
}
.underline-input {
width: 80rpx;
height: 32rpx;
line-height: 32rpx;
background-color: transparent;
border: none;
border-bottom: 2rpx solid white;
color: white;
font-size: 32rpx;
text-align: center;
outline: none;
padding: 0;
}
.underline-input:focus {
border-bottom: 3rpx solid #fff;
}
/* 响应式设计 */
@media (max-width: 750rpx) {
.form-row {
flex-direction: column;
gap: 20rpx;
}
.input-group {
margin-bottom: 15rpx;
}
}
.form-actions {
display: flex;
justify-content: center;
}
.submit-btn-image {
width: 306rpx;
height: 97rpx;
cursor: pointer;
transition: all 0.3s;
}
.submit-btn-image:hover {
transform: scale(1.05);
}
.submit-btn-image.disabled {
opacity: 0.5;
cursor: not-allowed;
}
.submit-btn-image.disabled:hover {
transform: none;
}
.close-button-area {
display: flex;
justify-content: center;
align-items: center;
padding: 30rpx 0;
z-index: 99;
}
.close-button-image {
width: 61rpx;
height: 60rpx;
cursor: pointer;
transition: all 0.3s;
}
.close-button-image:hover {
transform: scale(1.1);
}
</style>

View File

@ -0,0 +1,115 @@
<template>
<div class="couplet-display-modal" v-if="visible">
<div class="modal-overlay"></div>
<div class="modal-content couplet-content">
<div class="modal-body">
<div class="couplet-display">
<!-- 显示海报图片后端直接返回 -->
<div class="poster-image" v-if="couplet.image_url">
<img :src="couplet.image_url" alt="春联海报" style="width: 100%; max-width: 300px; height: auto;" />
</div>
</div>
</div>
</div>
<div class="close-button-area">
<div class="share-hint">长按海报分享</div>
<img
src="/static/images/btn_close.png"
class="close-button-image"
@click="$emit('close')"
/>
</div>
</div>
</template>
<script setup>
const props = defineProps({
visible: {
type: Boolean,
default: false
},
couplet: {
type: Object,
default: () => ({
share_url: '',
image_url: ''
})
}
})
const emit = defineEmits(['close'])
</script>
<style scoped>
.couplet-display-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
}
.modal-content {
position: relative;
border-radius: 8px;
overflow: hidden;
}
.couplet-content {
max-height: 80vh;
overflow-y: auto;
}
.couplet-display {
text-align: center;
}
.poster-image img {
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.share-hint {
margin-bottom: 10rpx;
font-size: 18px;
color: #fff;
text-align: center;
}
.close-button-area {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding-top: 20rpx;
z-index: 99;
}
.close-button-image {
width: 61rpx;
height: 60rpx;
cursor: pointer;
transition: all 0.3s;
}
.close-button-image:hover {
transform: scale(1.1);
}
</style>

View File

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

View File

@ -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(() => {
/>
<!-- AI春联生成弹窗 -->
<div class="form-modal" v-if="showAICoupletForm">
<div class="modal-overlay" @click="showAICoupletForm = false"></div>
<div class="modal-content">
<div class="modal-header">
<h3>AI春联生成</h3>
<button class="close-btn" @click="showAICoupletForm = false">×</button>
</div>
<div class="modal-body">
<p class="modal-description">输入两个字的关键词生成个性化春联</p>
<div class="form-group">
<input v-model="coupletKeyword" type="text" placeholder="请输入关键词(如:吉祥、如意)" maxlength="2" class="keyword-input" />
</div>
<div class="recommended-keywords">
<span v-for="word in recommendedKeywords" :key="word" @click="selectRecommendedKeyword(word)" class="keyword-tag">{{ word }}</span>
</div>
<div class="form-actions">
<button class="submit-btn" :disabled="!coupletKeyword || coupletKeyword.length < 2" @click="generateCouplet">生成春联</button>
</div>
</div>
</div>
</div>
<AICoupletForm
:visible="showAICoupletForm"
@close="showAICoupletForm = false"
@generate="handleGenerateCouplet"
/>
<!-- 春联展示页面 -->
<div class="couplet-display-modal" v-if="showCoupletDisplay">
<div class="modal-overlay" @click="showCoupletDisplay = false"></div>
<div class="modal-content couplet-content">
<div class="modal-header">
<h3>您的专属春联</h3>
<button class="close-btn" @click="showCoupletDisplay = false">×</button>
</div>
<div class="modal-body">
<div class="couplet-display">
<div class="couplet-item top-couplet">{{ generatedCouplet.top }}</div>
<div class="couplet-item bottom-couplet">{{ generatedCouplet.bottom }}</div>
<div class="couplet-item横批">{{ generatedCouplet.横批 }}</div>
</div>
<div class="collected-items-section" v-if="collectedItems.length > 0">
<h4 class="items-title">您收集的福印</h4>
<div class="collected-items">
<span v-for="(item, index) in collectedItems" :key="index" class="item-tag"><EFBFBD><EFBFBD> {{ item }}</span>
</div>
</div>
<div class="couplet-actions">
<button class="share-btn" @click="uni.showToast({title: '分享功能开发中...', icon: 'none', duration: 2000})"><EFBFBD><EFBFBD> 分享春联</button>
<button class="close-couplet-btn" @click="showCoupletDisplay = false"> 完成</button>
</div>
</div>
</div>
</div>
<CoupletDisplay
:visible="showCoupletDisplay"
:couplet="generatedCouplet"
@close="showCoupletDisplay = false"
@share="handleShareCouplet"
/>
</scroll-view>
</div>
</template>
@ -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;

View File

@ -2,6 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))

143
pages/info.vue Normal file
View File

@ -0,0 +1,143 @@
<template>
<view class="container">
<view class="form-row">
<!-- 上联输入区域 -->
<view class="input-group">
<text class="label">上联首字</text>
<input
class="underline-input"
type="text"
v-model="upperCouplet"
placeholder="______"
maxlength="1"
@input="onInputUpper"
/>
</view>
<!-- 下联输入区域 -->
<view class="input-group">
<text class="label">下联首字</text>
<input
class="underline-input"
type="text"
v-model="lowerCouplet"
placeholder="______"
maxlength="1"
@input="onInputLower"
/>
</view>
</view>
<!-- 显示输入结果 -->
<view class="result" v-if="showResult">
<text>输入结果{{ upperCouplet }}{{ lowerCouplet }}</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
upperCouplet: '', //
lowerCouplet: '' //
}
},
computed: {
showResult() {
return this.upperCouplet || this.lowerCouplet
}
},
methods: {
onInputUpper(e) {
//
console.log('上联输入:', this.upperCouplet)
},
onInputLower(e) {
//
console.log('下联输入:', this.lowerCouplet)
}
}
}
</script>
<style scoped>
.container {
background-color: #FF7F50; /* 橙色背景 */
min-height: 100vh; /* 全屏高度 */
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
}
.form-row {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
gap: 40px; /* 两个输入组之间的间距 */
flex-wrap: wrap; /* 小屏幕时自动换行 */
}
.input-group {
display: flex;
flex-direction: column;
align-items: center;
}
.label {
color: white; /* 白色文字 */
font-size: 18px;
margin-bottom: 10px;
font-weight: bold;
}
.underline-input {
width: 120px; /* 输入框宽度 */
height: 40px; /* 输入框高度 */
background-color: transparent; /* 透明背景 */
border: none; /* 去掉边框 */
border-bottom: 2px solid white; /* 白色下划线 */
color: white; /* 白色文字 */
font-size: 16px;
text-align: center;
outline: none; /* 去掉聚焦边框 */
padding: 5px;
}
/* 输入框占位符样式 */
.underline-input::placeholder {
color: rgba(255, 255, 255, 0.7); /* 半透明白色 */
}
/* 聚焦时的样式 */
.underline-input:focus {
border-bottom: 3px solid #fff; /* 加粗下划线 */
}
.result {
margin-top: 30px;
color: white;
font-size: 16px;
background-color: rgba(255, 255, 255, 0.2);
padding: 10px 20px;
border-radius: 5px;
}
/* 响应式设计 */
@media (max-width: 480px) {
.form-row {
flex-direction: column; /* 小屏幕时垂直排列 */
gap: 20px;
}
.input-group {
margin-bottom: 15px;
}
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB