测试activityPoster

This commit is contained in:
mac·ufutx 2026-04-15 14:32:27 +08:00
parent 7d1f0bc651
commit ac431927b7
3 changed files with 530 additions and 366 deletions

View File

@ -3,7 +3,7 @@ module.exports = {
title: '', title: '',
// baseApi: '//h5.ufutx.net/api' // 本地api请求地址,注意:如果你使用了代理,请设置成'/' // baseApi: '//h5.ufutx.net/api' // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
baseApi: '//sh5.ufutx.net/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/' baseApi: '//sh5.ufutx.net/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
baseApiV2: '//sh5.ufutx.net/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/' baseApiV2: '//love.ufutx.cn/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
baseApiV3: '//eval.fulllinkai.net/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/' baseApiV3: '//eval.fulllinkai.net/api', // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
// baseApi: '//api.ufutx.net/mock' // 本地api请求地址,注意:如果你使用了代理,请设置成'/' // baseApi: '//api.ufutx.net/mock' // 本地api请求地址,注意:如果你使用了代理,请设置成'/'
// APPID: 'xxx', // APPID: 'xxx',

View File

@ -1,92 +1,46 @@
<template> <template>
<div class="poster-generator-container"> <div class="poster-generator-container">
<!-- 表单选择区域 -->
<div class="input-section">
<van-field
v-model="selectedActivityTitle"
label="选择活动"
placeholder="请选择活动"
readonly
:label-width="60"
@click="showActivityPicker = true"
/>
<van-field <div class="input-card">
v-model="sponsorName" <div class="card-header">
label="主办方" <span class="header-icon">📋</span>
placeholder="请选择主办方" <span class="header-title">选择活动</span>
readonly </div>
:label-width="60" <div class="activity-selector" @click="showActivityPicker = true">
@click="showSponsorPicker = true" <div class="selector-label">活动名称</div>
/> <div class="selector-value" :class="{ placeholder: !selectedActivityTitle }">
<span class="selector-text">{{ selectedActivityTitle || '点击选择活动' }}</span>
</div>
<div class="selector-arrow">
<van-icon name="arrow" />
</div>
</div>
</div>
<!-- 活动选择弹窗 --> <!-- 活动选择弹窗 -->
<van-popup <van-popup v-model:show="showActivityPicker" position="bottom" round class="activity-popup">
v-model="showActivityPicker"
position="bottom"
round
class="activity-popup"
>
<div class="activity-picker-header"> <div class="activity-picker-header">
<span class="cancel-btn" @click="showActivityPicker = false">取消</span> <span class="cancel-btn" @click="showActivityPicker = false">取消</span>
<span class="title">选择活动</span> <span class="title">选择活动</span>
<span class="confirm-btn" @click="showActivityPicker = false">确定</span> <span class="confirm-btn" @click="showActivityPicker = false">确定</span>
</div> </div>
<div class="scroll-wrapper"> <div class="scroll-wrapper">
<van-list <van-list v-model:loading="listLoading" :finished="listFinished" finished-text="没有更多活动了"
v-model="listLoading" @load="onLoadMore">
:finished="listFinished" <div v-for="item in activityList" :key="item.id" class="activity-item"
finished-text="没有更多活动了" :class="{ active: selectedActivity?.id === item.id }" @click="onSelectActivity(item)">
@load="onLoadMore"
>
<div
v-for="item in activityList"
:key="item.id"
class="activity-item"
@click="onSelectActivity(item)"
>
<div class="activity-title">{{ item.title }}</div> <div class="activity-title">{{ item.title }}</div>
<div class="activity-time"> <div class="activity-time">{{ item.Subtitle }} | {{ formatDateToShow(item.end_time) }}</div>
{{ item.Subtitle }} | {{ formatDateToShow(item.end_time) }}
</div>
</div> </div>
</van-list> </van-list>
</div> </div>
</van-popup> </van-popup>
<!-- 主办方选择弹窗 --> <!-- 隐藏海报区域样式保持原样 -->
<van-popup
v-model="showSponsorPicker"
position="bottom"
round
class="activity-popup"
>
<div class="activity-picker-header">
<span class="cancel-btn" @click="showSponsorPicker = false">取消</span>
<span class="title">选择主办方</span>
<span class="confirm-btn" @click="showSponsorPicker = false">确定</span>
</div>
<div class="scroll-wrapper">
<div
v-for="(item, index) in sponsorList"
:key="index"
class="activity-item"
@click="onSelectSponsor(item)"
>
<div class="activity-title">{{ item }}</div>
</div>
</div>
</van-popup>
</div>
<!-- 海报生成容器隐藏布局 -->
<div ref="posterRef" class="poster-wrapper hidden-poster"> <div ref="posterRef" class="poster-wrapper hidden-poster">
<div class="poster-card"> <div class="poster-card">
<div class="poster-content"> <div class="poster-content">
<img <img src="https://images.health.ufutx.com/202604/13/8fa1b3080a60f97ef3f75e1370a6217c.jpeg" alt="海报背景图" />
src="https://images.health.ufutx.com/202604/13/8fa1b3080a60f97ef3f75e1370a6217c.jpeg"
alt="海报背景"
/>
<div class="poster-content-wrapper"> <div class="poster-content-wrapper">
<div class="poster-title"> <div class="poster-title">
<p class="poster-title-text">{{ selectedActivity?.Subtitle || '河南·襄城站' }}</p> <p class="poster-title-text">{{ selectedActivity?.Subtitle || '河南·襄城站' }}</p>
@ -117,135 +71,119 @@
</div> </div>
</div> </div>
<!-- 操作按钮 --> <!-- 操作按钮 -->
<div class="btn-group"> <div class="btn-group">
<button <button class="btn btn-primary" :disabled="isGenerating" @click="generatePoster">
class="btn btn-primary" <van-loading v-if="isGenerating" size="20px" color="#fff" />
:disabled="isGenerating" <span v-else> 生成海报</span>
@click="generatePoster"
>
<span v-if="!isGenerating">生成海报</span>
<span v-else>生成中...</span>
</button>
<button
v-if="posterImg"
class="btn btn-secondary"
@click="downloadPoster"
>
下载海报
</button> </button>
<button v-if="posterImg && !isWeChatEnv" class="btn btn-secondary" @click="downloadPoster"> 📥 下载海报</button>
</div> </div>
<!-- 海报预览 --> <!-- 生成预览区域 -->
<div v-if="posterImg" class="generated-poster-preview"> <div v-if="posterImg" class="generated-poster-preview">
<h4 class="preview-title">海报生成成功长按保存/点击预览</h4> <div class="preview-header">
<img :src="posterImg" class="preview-img" @click="showImagePreview" /> <div class="preview-icon">🎉</div>
<h4 class="preview-title">{{ isWeChatEnv ? '长按图片保存到手机' : '点击图片预览大图' }}</h4>
</div>
<img :src="posterImg" alt="生成的海报" class="preview-img" @click="showImagePreviewFn(posterImg)" />
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import { nextTick, onMounted, ref, watch } from 'vue'
import html2canvas from 'html2canvas' import html2canvas from 'html2canvas'
import { showDialog, showImagePreview, showLoadingToast, showToast } from 'vant'
import requestApp from '@/utils/requestApp'
import QRCode from 'qrcode' import QRCode from 'qrcode'
import axios from 'axios' import { weXinShare } from '@/plugins/wxShare'
export default { // 🔥
name: 'ActivityPoster', const sponsorName = ref('')
data() {
return {
//
sponsorName: '友福同享(襄城县)智能科技有限公司',
sponsorList: [
'友福同享(深圳)智能科技有限公司',
'友福粤越(惠州)智能科技有限公司',
'友福同享(南昌)智能科技有限公司',
'友福同享(天津)智能科技有限公司',
'友福同享(襄城县)智能科技有限公司',
'友福同享(哈尔滨)智能科技有限公司'
],
showSponsorPicker: false,
// const posterRef = ref(null)
activityList: [], const posterImg = ref('')
selectedActivity: null, const isGenerating = ref(false)
selectedActivityTitle: '', const isWeChatEnv = ref(false)
showActivityPicker: false, const qrcodeCanvas = ref(null)
listLoading: false,
listFinished: false,
// const activityList = ref([])
posterImg: '', const selectedActivity = ref(null)
isGenerating: false, const selectedActivityTitle = ref('')
isWeChatEnv: false const showActivityPicker = ref(false)
} const listLoading = ref(false)
}, const listFinished = ref(true)
mounted() { const openId = ref('')
this.detectWeChatEnv()
this.getActivityList() // +
}, watch(selectedActivity, async (newVal) => {
watch: { if (newVal && newVal.id && qrcodeCanvas.value) {
selectedActivity: { //
handler: async function(val) { sponsorName.value = newVal.sponsor || ''
if (val && val.id && this.$refs.qrcodeCanvas) { openId.value = localStorage.getItem('openid')
const openId = localStorage.getItem('openid') || '' const link = `https://love.ufutx.cn/api/official/live/wechat/FamilyAuth?merchant_id=44&serve_tab=&from_openid=${openId.value}&url=https%3A%2F%2Flove.ufutx.cn%2Fpu%2F%23%2FactivityDetails%2F${newVal.id}`
const link = `https://love.ufutx.cn/api/official/live/wechat/FamilyAuth?merchant_id=44&serve_tab=&from_openid=${openId}&url=https%3A%2F%2Flove.ufutx.cn%2Fpu%2F%23%2FactivityDetails%2F${val.id}` await QRCode.toCanvas(qrcodeCanvas.value, link, {
await QRCode.toCanvas(this.$refs.qrcodeCanvas, link, {
width: 76, width: 76,
margin: 1, margin: 1,
color: { dark: '#000000', light: '#ffffff' } color: { dark: '#000000', light: '#ffffff' }
}) })
} }
}, })
immediate: true
}
},
methods: {
//
async getActivityList() {
this.listLoading = true
try {
const res = await axios({
url: '/sh5/uftx/community/activity/list',
method: 'get'
})
if (res.code === 0 && Array.isArray(res.data)) {
this.activityList = res.data
} else {
this.$toast('活动列表获取失败')
}
} catch (err) {
console.error(err)
this.$toast('网络异常,请重试')
} finally {
this.listLoading = false
this.listFinished = true
}
},
//
onSelectActivity(item) {
this.selectedActivity = item
this.selectedActivityTitle = item.title
this.showActivityPicker = false
},
//
onSelectSponsor(item) {
this.sponsorName = item
this.showSponsorPicker = false
},
onLoadMore() {
this.listFinished = true
},
// const getData = () => {
formatDateToShow(timeStr) { listLoading.value = true
weXinShare('https://image.fulllinkai.com/202310/28/88e931a50ec0a8094fb46191b389457e.png', `https://health.ufutx.cn/go_html/role_apply#/activityPoster`, '活动海报生成「saas」', '查看详情')
requestApp({ url: '/sh5/uftx/community/activity/list', method: 'get' })
.then((res) => {
if (res.code === 0 && Array.isArray(res.data)) {
activityList.value = res.data
//
// if (res.data.length > 0) {
// onSelectActivity(res.data[0]);
// }
} else {
showToast('活动列表获取失败')
}
})
.catch((err) => {
console.error('获取活动列表失败:', err)
showToast('活动列表获取失败,请重试')
})
.finally(() => {
listLoading.value = false
listFinished.value = true
})
}
const detectWeChatEnv = () => {
const userAgent = navigator.userAgent.toLowerCase()
isWeChatEnv.value = /micromessenger/.test(userAgent)
}
//
const onSelectActivity = (item) => {
selectedActivity.value = item
selectedActivityTitle.value = item.title
showActivityPicker.value = false
generatePoster()
}
const onLoadMore = () => {
listLoading.value = false
listFinished.value = true
}
const formatDateToShow = (timeStr) => {
if (!timeStr) return '' if (!timeStr) return ''
const date = new Date(timeStr) const date = new Date(timeStr)
const year = date.getFullYear() const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0') const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0')
return `${year}${month}${day}` return `${year}${month}${day}`
}, }
formatDateToChinese(timeStr) {
const formatDateToChinese = (timeStr) => {
if (!timeStr) { if (!timeStr) {
const now = new Date() const now = new Date()
const year = now.getFullYear() const year = now.getFullYear()
@ -256,133 +194,83 @@ export default {
const [datePart] = timeStr.split(' ') const [datePart] = timeStr.split(' ')
const [year, month, day] = datePart.split('-') const [year, month, day] = datePart.split('-')
return `${year}/${month}/${day}` return `${year}/${month}/${day}`
}, }
formatTime(timeStr) {
const formatTime = (timeStr) => {
if (!timeStr) return '14:00' if (!timeStr) return '14:00'
const [, timePart] = timeStr.split(' ') const [, timePart] = timeStr.split(' ')
return timePart.slice(0, 5) return timePart.slice(0, 5)
}, }
// const showImagePreviewFn = (img) => {
detectWeChatEnv() { return showImagePreview([img])
this.isWeChatEnv = /micromessenger/.test(navigator.userAgent.toLowerCase()) }
},
//
showImagePreview() {
window.open(this.posterImg)
},
// const generatePoster = async () => {
async generatePoster() { try {
if (!this.selectedActivity) { if (!selectedActivity.value) {
this.$dialog.alert({ message: '请先选择活动!' }) showDialog({ message: '请先选择活动!' })
return return
} }
try { isGenerating.value = true
this.isGenerating = true const toast = showLoadingToast({
await this.$nextTick() message: '海报生成中...',
const canvas = await html2canvas(this.$refs.posterRef, { forbidClick: true,
duration: 0
})
await nextTick()
const posterElement = posterRef.value
if (!posterElement) {
isGenerating.value = false
toast.close()
return
}
const canvas = await html2canvas(posterElement, {
useCORS: true, useCORS: true,
scale: 3, scale: 3,
backgroundColor: null, backgroundColor: null,
logging: false logging: false,
imageTimeout: 10000
}) })
this.posterImg = canvas.toDataURL('image/png')
this.$toast('海报生成成功')
} catch (err) {
console.error(err)
this.$dialog.alert({ message: '生成失败,请重试' })
} finally {
this.isGenerating = false
}
},
// posterImg.value = canvas.toDataURL('image/png', 1.0)
downloadPoster() { toast.close()
const link = document.createElement('a') showToast({ message: '海报生成成功!', icon: 'success' })
link.href = this.posterImg } catch (err) {
const fileName = `${this.selectedActivity.title || '活动'}海报.png` console.error('生成海报失败:', err)
link.download = fileName.replace(/[^a-zA-Z0-9_\u4e00-\u9fa5]/g, '') showToast({ message: '生成海报失败,请重试!', icon: 'fail' })
link.click() } finally {
} isGenerating.value = false
} }
} }
function downloadPoster() {
if (!posterImg.value) return
const link = document.createElement('a')
link.href = posterImg.value
// /
const fileName = `[${selectedActivity.value?.title || '活动'}]海报.png`
link.download = fileName.replace(/[\\/:*?"<>|]/g, '')
link.click()
URL.revokeObjectURL(link.href)
}
onMounted(() => {
detectWeChatEnv()
getData()
})
</script> </script>
<style scoped lang="scss"> <style scoped>
/* 基础样式 */ img {
.poster-generator-container { display: inline-block !important;
width: 100%; max-width: 100%;
min-height: 100vh; max-height: 100%;
padding: 20px;
background: #f5f7fa;
box-sizing: border-box;
} }
.input-section {
margin-bottom: 30px;
}
/* 弹窗样式 */
.activity-popup {
height: 80vh;
display: flex;
flex-direction: column;
}
.activity-picker-header {
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #f0f0f0;
}
.scroll-wrapper {
flex: 1;
overflow-y: auto;
}
.cancel-btn {
color: #999;
font-size: 14px;
}
.confirm-btn {
color: #18ca6e;
font-size: 14px;
font-weight: 500;
}
.title {
font-size: 16px;
color: #2c3e50;
}
.activity-item {
padding: 16px;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
&:active {
background: #f5f7fa;
}
}
.activity-title {
font-size: 15px;
color: #2c3e50;
margin-bottom: 4px;
}
.activity-time {
font-size: 12px;
color: #999;
}
/* 海报隐藏容器 */
.poster-wrapper { .poster-wrapper {
width: 375px; width: 375px;
height: 788px; height: 788px;
@ -394,40 +282,59 @@ export default {
left: -9999px; left: -9999px;
top: -9999px; top: -9999px;
z-index: -1; z-index: -1;
pointer-events: none;
} }
.poster-card { .poster-card {
width: 100%; width: 100%;
height: 100%; height: 100%;
color: #ffffff;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-between;
background: #333333;
position: relative; position: relative;
} }
.poster-content img {
object-fit: cover;
}
.poster-content { .poster-content {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex;
flex-direction: column;
gap: 20px;
position: relative; position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
} }
/* 海报文字样式 */
.poster-title { .poster-title {
position: absolute; position: absolute;
left: 0; left: 0;
top: 262px; top: 262px;
width: 100%; width: 100%;
text-align: center; letter-spacing: 4px;
font-size: 20px;
font-weight: 400;
display: flex;
justify-content: center;
padding: 0;
} }
.poster-title-text { .poster-title .poster-title-text {
color: #0d2f73; text-align: center;
font-size: 20px;
width: 300px; width: 300px;
margin: 0 auto; color: #0d2f73;
line-height: 1.6;
padding: 2px 0;
margin: 0;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
} }
.sponsor-name { .sponsor-name {
@ -435,9 +342,15 @@ export default {
left: 0; left: 0;
top: 446px; top: 446px;
width: 100%; width: 100%;
text-align: center; letter-spacing: 0.8px;
color: #0e2965; display: flex;
justify-content: center;
padding: 0;
}
.sponsor-name .name {
font-size: 14px; font-size: 14px;
color: #0e2965;
} }
.poster-qrcode { .poster-qrcode {
@ -447,14 +360,16 @@ export default {
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: 0;
} }
.poster-qrcode-image { .poster-qrcode-image {
width: 76px; width: 76px;
height: 76px; height: 76px;
background: #fff;
padding: 3px;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
background-color: #ffffff;
padding: 3px;
} }
.poster-time-wrap { .poster-time-wrap {
@ -463,20 +378,25 @@ export default {
top: 630px; top: 630px;
width: 100%; width: 100%;
text-align: center; text-align: center;
color: #0e2866; justify-content: center;
letter-spacing: 1px;
} }
.poster-date { .poster-date {
font-size: 24px; font-size: 24px;
font-weight: 500;
color: #0e2866;
} }
.poster-time { .poster-time {
font-weight: 400;
font-size: 22px; font-size: 22px;
color: #0e2866;
margin-top: 14px; margin-top: 14px;
}
.start-time { .start-time {
margin-right: 30px; margin-right: 30px;
}
} }
.poster-address { .poster-address {
@ -484,70 +404,314 @@ export default {
left: 0; left: 0;
top: 720px; top: 720px;
width: 100%; width: 100%;
max-height: 50px;
display: flex; display: flex;
text-align: center;
justify-content: center; justify-content: center;
letter-spacing: 1px;
} }
._text-warp { .poster-address ._text-warp {
max-width: 80%; max-width: 80%;
background: #fff; font-size: 14px;
padding: 4px 16px;
border-radius: 32px;
color: #0f2967; color: #0f2967;
font-weight: 500;
background: white;
border-radius: 32px;
padding: 4px 16px;
display: flex; display: flex;
justify-content: space-between;
} }
._title { .poster-address ._title {
width: 76px; width: 76px;
flex-shrink: 0; flex-shrink: 0;
} }
/* 按钮样式 */ .poster-address ._text {
text-align: left;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
/* ========== 以下为美化后的外部样式(选择器、按钮、预览区域) ========== */
.poster-generator-container {
min-height: 100vh;
padding: 20px 16px 40px;
background: linear-gradient(135deg, #f5f7fa 0%, #e9eef3 100%);
display: flex;
flex-direction: column;
align-items: center;
gap: 24px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
}
/* 输入卡片 */
.input-card {
width: 100%;
max-width: 400px;
background: #ffffff;
border-radius: 24px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.06);
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.input-card:hover {
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
align-items: center;
gap: 8px;
padding: 16px 20px 0;
font-size: 16px;
font-weight: 600;
color: #1f2937;
}
.header-icon {
font-size: 20px;
}
.header-title {
background: linear-gradient(135deg, #18ca6e, #0f9d58);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.activity-selector {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 20px 12px 20px;
cursor: pointer;
transition: background 0.2s;
}
.activity-selector:active {
background: #f8fafc;
}
.selector-label {
font-size: 14px;
color: #6b7280;
font-weight: 500;
flex-shrink: 0;
}
.selector-value {
flex: 1;
margin: 0 12px;
font-size: 15px;
font-weight: 500;
color: #1f2937;
text-align: right;
display: flex;
justify-content: flex-end;
align-items: center;
min-height: 42px;
}
.selector-text {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
max-width: 100%;
line-height: 1.4;
}
.selector-value.placeholder .selector-text {
color: #9ca3af;
font-weight: 400;
}
.selector-arrow {
color: #9ca3af;
font-size: 14px;
flex-shrink: 0;
}
/* 弹窗样式 */
.activity-popup {
height: 70vh;
display: flex;
flex-direction: column;
border-radius: 20px 20px 0 0;
overflow: hidden;
}
.activity-picker-header {
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid #f0f0f0;
background: #fff;
}
.cancel-btn {
font-size: 15px;
color: #9ca3af;
cursor: pointer;
}
.title {
font-size: 17px;
font-weight: 600;
color: #1f2937;
}
.confirm-btn {
font-size: 15px;
color: #18ca6e;
font-weight: 600;
cursor: pointer;
}
.scroll-wrapper {
flex: 1;
overflow-y: auto;
}
.activity-item {
padding: 16px 20px;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
transition: background 0.2s;
}
.activity-item:active {
background: #f8fafc;
}
.activity-item.active {
background: #f0fdf4;
border-left: 3px solid #18ca6e;
}
.activity-title {
font-size: 15px;
font-weight: 600;
color: #1f2937;
margin-bottom: 6px;
}
.activity-time {
font-size: 12px;
color: #9ca3af;
}
/* 按钮组 */
.btn-group { .btn-group {
display: flex; display: flex;
gap: 15px; gap: 16px;
margin: 30px 0; width: 100%;
max-width: 400px;
} }
.btn { .btn {
flex: 1; flex: 1;
padding: 12px 0; padding: 14px 20px;
border: none; border: none;
border-radius: 8px; border-radius: 60px;
font-size: 16px; font-size: 16px;
color: #fff; font-weight: 600;
cursor: pointer; cursor: pointer;
transition: all 0.25s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
} }
.btn-primary { .btn-primary {
background: #18ca6e; background: linear-gradient(135deg, #18ca6e, #0f9d58);
color: white;
box-shadow: 0 4px 12px rgba(24, 202, 110, 0.3);
}
&:disabled { .btn-primary:active {
background: #95d8b7; transform: scale(0.97);
box-shadow: 0 2px 6px rgba(24, 202, 110, 0.3);
}
.btn-primary:disabled {
opacity: 0.7;
transform: none;
cursor: not-allowed; cursor: not-allowed;
}
} }
.btn-secondary { .btn-secondary {
background: #2c3e50; background: #2c3e50;
color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.btn-secondary:active {
transform: scale(0.97);
} }
/* 预览区域 */ /* 预览区域 */
.generated-poster-preview { .generated-poster-preview {
background: #fff; width: 100%;
max-width: 400px;
background: white;
border-radius: 28px;
padding: 20px; padding: 20px;
border-radius: 12px; box-shadow: 0 12px 30px rgba(0, 0, 0, 0.1);
text-align: center; transition: all 0.3s;
}
.preview-header {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
}
.preview-icon {
font-size: 22px;
} }
.preview-title { .preview-title {
color: #2c3e50; font-size: 15px;
margin-bottom: 15px; font-weight: 500;
font-size: 16px; color: #374151;
margin: 0;
} }
.preview-img { .preview-img {
width: 100%; width: 100%;
border-radius: 8px; border-radius: 16px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.2s;
}
.preview-img:active {
transform: scale(0.99);
}
/* 适配小屏幕 */
@media (max-width: 420px) {
.poster-generator-container {
padding: 16px 12px 32px;
}
.btn {
padding: 12px 16px;
font-size: 14px;
}
} }
</style> </style>

View File

@ -52,7 +52,7 @@ module.exports = {
productionSourceMap: false, // 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。 productionSourceMap: false, // 如果你不需要生产环境的 source map可以将其设置为 false 以加速生产环境构建。
devServer: { devServer: {
port: 8080, // 端口 port: 8080, // 端口
open: false, // 启动后打开浏览器 open: true, // 启动后打开浏览器
overlay: { overlay: {
// 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层 // 当出现编译器错误或警告时,在浏览器中显示全屏覆盖层
warnings: false, warnings: false,