更新页面效果
This commit is contained in:
@@ -29,10 +29,10 @@
|
||||
</header>
|
||||
|
||||
<div class="max-w-[960px] mx-auto px-4 py-6">
|
||||
<a-tabs v-model:activeKey="activeTab" type="card" class="admin-tabs">
|
||||
<a-tabs v-model:activeKey="activeTab" type="card" class="admin-tabs" :destroyInactiveTabPane="true">
|
||||
<!-- 基础信息 -->
|
||||
<a-tab-pane key="basic" tab="基础信息">
|
||||
<a-card :bordered="false" class="!shadow-sm">
|
||||
<a-card v-if="activeTab === 'basic'" :bordered="false" class="!shadow-sm">
|
||||
<a-form layout="vertical">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||
<a-form-item label="新郎">
|
||||
@@ -54,16 +54,23 @@
|
||||
<a-textarea v-model:value="cfg.address" :rows="2" placeholder="可换行" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<a-form-item label="封面 / 尾页主图">
|
||||
<image-field v-model="cfg.heroImg" @pick="openUpload(u => cfg.heroImg = u)" :uploading="uploading" />
|
||||
</a-form-item>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||
<a-form-item label="封面图片">
|
||||
<image-field v-model="cfg.heroImg" label="封面" @pick="openUpload(u => cfg.heroImg = u)" :uploading="uploading" />
|
||||
<div class="text-[11px] text-[#8A8680] mt-2">用于首页封面和入场揭幕背景。</div>
|
||||
</a-form-item>
|
||||
<a-form-item label="尾页图片">
|
||||
<image-field v-model="cfg.endImg" label="尾页" @pick="openUpload(u => cfg.endImg = u)" :uploading="uploading" />
|
||||
<div class="text-[11px] text-[#8A8680] mt-2">用于最后的回执尾页,可与封面分开设置。</div>
|
||||
</a-form-item>
|
||||
</div>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 文案 -->
|
||||
<a-tab-pane key="copy" tab="邀请文案">
|
||||
<a-card :bordered="false" class="!shadow-sm">
|
||||
<a-card v-if="activeTab === 'copy'" :bordered="false" class="!shadow-sm">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="竖排文案 · 上联">
|
||||
<a-input v-model:value="cfg.formalText1" placeholder="如 两姓联姻 一堂缔约" />
|
||||
@@ -77,7 +84,7 @@
|
||||
|
||||
<!-- 视觉 / 动效 -->
|
||||
<a-tab-pane key="visual" tab="视觉与动效">
|
||||
<a-card :bordered="false" class="!shadow-sm">
|
||||
<a-card v-if="activeTab === 'visual'" :bordered="false" class="!shadow-sm">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="背景音乐">
|
||||
<div class="text-[12px] text-[#8A8680] leading-relaxed">多首背景音乐的上传、试听与「设为播放」请在 <span class="text-[#a88c6b]">背景音乐</span> 标签页管理。</div>
|
||||
@@ -122,7 +129,7 @@
|
||||
|
||||
<!-- 背景音乐 -->
|
||||
<a-tab-pane key="music" tab="背景音乐">
|
||||
<a-card :bordered="false" class="!shadow-sm">
|
||||
<a-card v-if="activeTab === 'music'" :bordered="false" class="!shadow-sm">
|
||||
<div class="text-[12px] text-[#8A8680] leading-relaxed mb-4">
|
||||
上传多首背景音乐(支持 mp3 等格式),访客首次交互后自动播放当前曲目,可在前台随时切换或暂停。
|
||||
</div>
|
||||
@@ -147,14 +154,67 @@
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 相册页 -->
|
||||
<!-- 上传设置 -->
|
||||
<a-tab-pane key="upload" tab="上传设置">
|
||||
<a-card v-if="activeTab === 'upload'" :bordered="false" class="!shadow-sm">
|
||||
<a-alert
|
||||
type="info"
|
||||
show-icon
|
||||
class="mb-4"
|
||||
message="上传设置仅后台可见,OSS 密钥不会写入公开请柬配置。"
|
||||
/>
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="上传方式">
|
||||
<a-radio-group v-model:value="uploadCfg.provider">
|
||||
<a-radio value="local">本地上传</a-radio>
|
||||
<a-radio value="oss">阿里云 OSS</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<div v-if="uploadCfg.provider === 'oss'" class="grid grid-cols-1 md:grid-cols-2 gap-x-6">
|
||||
<a-form-item label="AccessKeyId">
|
||||
<a-input v-model:value="uploadCfg.accessKeyId" placeholder="请输入 AccessKeyId" />
|
||||
</a-form-item>
|
||||
<a-form-item label="AccessKeySecret">
|
||||
<a-input-password v-model:value="uploadCfg.accessKeySecret" :placeholder="uploadCfg.hasSecret ? '已配置,留空则不修改' : '请输入 AccessKeySecret'" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Bucket">
|
||||
<a-input v-model:value="uploadCfg.bucket" placeholder="如 wedding-assets" />
|
||||
</a-form-item>
|
||||
<a-form-item label="文件夹">
|
||||
<a-input v-model:value="uploadCfg.folder" placeholder="如 invitations" />
|
||||
</a-form-item>
|
||||
<a-form-item label="访问域名">
|
||||
<a-input v-model:value="uploadCfg.domain" placeholder="如 https://cdn.example.com,可留空" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Region">
|
||||
<a-input v-model:value="uploadCfg.region" placeholder="如 cn-hangzhou" />
|
||||
</a-form-item>
|
||||
<a-form-item label="Endpoint">
|
||||
<a-input v-model:value="uploadCfg.endpoint" placeholder="如 https://oss-cn-hangzhou.aliyuncs.com" />
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div v-else class="text-[12px] text-[#8A8680] leading-relaxed mb-4">
|
||||
本地上传会继续保存到后端 uploads 目录,返回 http://localhost:8080/uploads/ 开头的地址。
|
||||
</div>
|
||||
<a-button type="primary" :loading="uploadConfigSaving" @click="onSaveUploadConfig"
|
||||
class="!bg-[#a88c6b] hover:!bg-[#967b5c] !border-[#a88c6b]">
|
||||
<i class="fa-solid fa-floppy-disk mr-1"></i>保存上传设置
|
||||
</a-button>
|
||||
</a-form>
|
||||
</a-card>
|
||||
</a-tab-pane>
|
||||
|
||||
<a-tab-pane key="photos" tab="相册页管理">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-[12px] text-[#8A8680]">共 {{ cfg.photoPages.length }} 个相册页</span>
|
||||
<a-button size="small" @click="addPage"><i class="fa-solid fa-plus mr-1 text-[#a88c6b]"></i>新增相册页</a-button>
|
||||
</div>
|
||||
<a-empty v-if="!cfg.photoPages.length" description="暂无相册页" />
|
||||
<a-card v-for="(page, idx) in cfg.photoPages" :key="idx" :bordered="false" class="!shadow-sm mb-4">
|
||||
<a-tabs v-if="cfg.photoPages.length" v-model:activeKey="activePhotoTab" size="small" type="card" class="photo-page-tabs" :destroyInactiveTabPane="true">
|
||||
<a-tab-pane v-for="(page, idx) in cfg.photoPages" :key="String(idx)" :tab="`第 ${idx + 1} 页`" />
|
||||
</a-tabs>
|
||||
<template v-for="(page, idx) in cfg.photoPages" :key="idx">
|
||||
<a-card v-if="idx === currentPhotoIndex" :bordered="false" class="!shadow-sm mb-4">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<span class="text-[#a88c6b] text-sm">第 {{ idx + 1 }} 页</span>
|
||||
<div class="flex gap-1">
|
||||
@@ -257,6 +317,7 @@
|
||||
</template>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
</a-tab-pane>
|
||||
|
||||
<!-- 婚礼流程 -->
|
||||
@@ -303,10 +364,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { computed, reactive, ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { getConfig, saveConfig, uploadImage, getRsvpList } from '@/api/wedding'
|
||||
import { getConfig, saveConfig, uploadImage, getRsvpList, getUploadConfig, saveUploadConfig } from '@/api/wedding'
|
||||
import { useAdminStore } from '@/stores/admin'
|
||||
import ImageField from '@/components/ImageField.vue'
|
||||
import { BORDER_STYLES, borderClass } from '@/composables/borderStyles'
|
||||
@@ -315,12 +376,16 @@ const router = useRouter()
|
||||
const adminStore = useAdminStore()
|
||||
|
||||
const activeTab = ref('basic')
|
||||
const activePhotoTab = ref('0')
|
||||
const saving = ref(false)
|
||||
const uploading = ref(false)
|
||||
const uploadConfigSaving = ref(false)
|
||||
const rsvpLoading = ref(false)
|
||||
const rsvpList = ref([])
|
||||
|
||||
const cfg = reactive(defaultConfig())
|
||||
const uploadCfg = reactive(defaultUploadConfig())
|
||||
const currentPhotoIndex = computed(() => Math.min(Number(activePhotoTab.value) || 0, Math.max(cfg.photoPages.length - 1, 0)))
|
||||
|
||||
function defaultConfig() {
|
||||
return {
|
||||
@@ -328,6 +393,7 @@ function defaultConfig() {
|
||||
hotel: '杭州星光万丽大酒店', address: '杭州市西湖区星光大道88号\n3楼大宴会厅',
|
||||
formalText1: '两姓联姻 一堂缔约', formalText2: '良缘永结 匹配同称',
|
||||
heroImg: 'https://images.unsplash.com/photo-1583939003579-730e3918a45a?auto=format&fit=crop&w=800&q=80',
|
||||
endImg: 'https://images.unsplash.com/photo-1519741497674-611481863552?auto=format&fit=crop&w=800&q=80',
|
||||
musicList: [{ url: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3', name: '背景音乐 1' }],
|
||||
activeMusicUrl: 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3',
|
||||
scrollMode: 'snap', petalEnabled: true, bubbleEnabled: true, introEnabled: true,
|
||||
@@ -346,6 +412,20 @@ function defaultConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function defaultUploadConfig() {
|
||||
return {
|
||||
provider: 'local',
|
||||
accessKeyId: '',
|
||||
accessKeySecret: '',
|
||||
hasSecret: false,
|
||||
bucket: '',
|
||||
folder: '',
|
||||
domain: '',
|
||||
region: '',
|
||||
endpoint: '',
|
||||
}
|
||||
}
|
||||
|
||||
function fill(n) {
|
||||
return Array.from({ length: n }, () => 'https://images.unsplash.com/photo-1511285560929-80b456fea0bc?auto=format&fit=crop&w=400&q=80')
|
||||
}
|
||||
@@ -411,9 +491,24 @@ function moveImage(page, i, dir) {
|
||||
}
|
||||
|
||||
// 相册页操作
|
||||
function addPage() { cfg.photoPages.push(ensurePageShape({ type: 'single', title: '新页面', subtitle: 'NEW', desc: '', img1: '' })) }
|
||||
function removePage(i) { cfg.photoPages.splice(i, 1) }
|
||||
function movePage(i, dir) { const j = i + dir; if (j < 0 || j >= cfg.photoPages.length) return; const t = cfg.photoPages[i]; cfg.photoPages[i] = cfg.photoPages[j]; cfg.photoPages[j] = t }
|
||||
function setActivePhotoIndex(i) {
|
||||
const max = Math.max(cfg.photoPages.length - 1, 0)
|
||||
activePhotoTab.value = String(Math.min(Math.max(i, 0), max))
|
||||
}
|
||||
function addPage() {
|
||||
cfg.photoPages.push(ensurePageShape({ type: 'single', title: '新页面', subtitle: 'NEW', desc: '', img1: '' }))
|
||||
setActivePhotoIndex(cfg.photoPages.length - 1)
|
||||
}
|
||||
function removePage(i) {
|
||||
cfg.photoPages.splice(i, 1)
|
||||
setActivePhotoIndex(Math.min(i, cfg.photoPages.length - 1))
|
||||
}
|
||||
function movePage(i, dir) {
|
||||
const j = i + dir
|
||||
if (j < 0 || j >= cfg.photoPages.length) return
|
||||
const t = cfg.photoPages[i]; cfg.photoPages[i] = cfg.photoPages[j]; cfg.photoPages[j] = t
|
||||
setActivePhotoIndex(j)
|
||||
}
|
||||
// 流程操作
|
||||
function addSchedule() { cfg.schedule.push({ time: '', event: '', desc: '' }) }
|
||||
function removeSchedule(i) { cfg.schedule.splice(i, 1) }
|
||||
@@ -473,6 +568,40 @@ function shortUrl(url) {
|
||||
return parts[parts.length - 1] || url
|
||||
}
|
||||
|
||||
async function loadUploadConfig() {
|
||||
try {
|
||||
const res = await getUploadConfig()
|
||||
Object.assign(uploadCfg, defaultUploadConfig(), res.data || {})
|
||||
uploadCfg.accessKeySecret = ''
|
||||
} catch (e) {
|
||||
// 上传设置仅后台使用,读取失败不影响内容编辑。
|
||||
}
|
||||
}
|
||||
|
||||
async function onSaveUploadConfig() {
|
||||
uploadConfigSaving.value = true
|
||||
try {
|
||||
const payload = {
|
||||
provider: uploadCfg.provider,
|
||||
accessKeyId: uploadCfg.accessKeyId,
|
||||
accessKeySecret: uploadCfg.accessKeySecret,
|
||||
bucket: uploadCfg.bucket,
|
||||
folder: uploadCfg.folder,
|
||||
domain: uploadCfg.domain,
|
||||
region: uploadCfg.region,
|
||||
endpoint: uploadCfg.endpoint,
|
||||
}
|
||||
const res = await saveUploadConfig(payload)
|
||||
Object.assign(uploadCfg, defaultUploadConfig(), res.data || {})
|
||||
uploadCfg.accessKeySecret = ''
|
||||
message.success('上传设置已保存')
|
||||
} catch (e) {
|
||||
message.error(e?.message || '上传设置保存失败')
|
||||
} finally {
|
||||
uploadConfigSaving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 保存
|
||||
async function onSave() {
|
||||
saving.value = true
|
||||
@@ -526,14 +655,19 @@ onMounted(async () => {
|
||||
loaded.musicList = [{ url: loaded.musicUrl, name: '背景音乐' }]
|
||||
loaded.activeMusicUrl = loaded.musicUrl
|
||||
}
|
||||
loaded.endImg = loaded.endImg || loaded.heroImg || cfg.endImg
|
||||
Object.assign(cfg, loaded)
|
||||
cfg.photoPages = (cfg.photoPages || []).map(ensurePageShape)
|
||||
setActivePhotoIndex(currentPhotoIndex.value)
|
||||
// 数值型参数兜底(兼容旧配置)
|
||||
cfg.freeScrollInterval = Number(cfg.freeScrollInterval) || 50
|
||||
cfg.pageSwitchDuration = Number(cfg.pageSwitchDuration) || 800
|
||||
}
|
||||
} catch (e) { /* 使用默认配置 */ }
|
||||
if (adminStore.isAdmin) loadRsvp()
|
||||
if (adminStore.isAdmin) {
|
||||
loadRsvp()
|
||||
loadUploadConfig()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user