493 lines
13 KiB
Vue
Raw Normal View History

2025-07-19 20:00:08 +08:00
<template>
<view class="page-container" :style="containerStyle">
<common-header title="发布活动" theme="activity" @back="goBack" />
<view class="add-content">
<!-- 表单卡片 -->
<view class="form-card">
<uni-forms ref="form" :model="formData" :rules="rules" label-width="120">
<!-- 基础信息区域 -->
<view class="form-section">
<view class="section-title">
<uni-icons type="info" size="16" color="#ff5722" />
<text>基础信息</text>
</view>
<uni-forms-item label="活动标题" name="title" required>
<uni-easyinput
v-model="formData.title"
placeholder="请输入活动标题"
maxlength="50"
:clearable="true" />
</uni-forms-item>
<uni-forms-item label="活动描述" name="description">
<uni-easyinput
v-model="formData.description"
type="textarea"
placeholder="请输入活动描述,让用户了解活动详情"
maxlength="200"
:auto-height="true" />
</uni-forms-item>
</view>
<!-- 时间设置区域 -->
<view class="form-section">
<view class="section-title">
<uni-icons type="calendar" size="16" color="#ff5722" />
<text>时间设置</text>
</view>
<uni-forms-item label="活动时间" name="startTime" required>
<view class="datetime-wrapper">
<uni-datetime-picker
v-model="formData.startTime"
type="datetime"
placeholder="选择开始时间"
:clear-icon="true" />
<uni-icons type="calendar-filled" size="16" color="#999" />
</view>
</uni-forms-item>
<uni-forms-item label="结束时间" name="endTime" required>
<view class="datetime-wrapper">
<uni-datetime-picker
v-model="formData.endTime"
type="datetime"
placeholder="选择结束时间"
:clear-icon="true" />
<uni-icons type="calendar-filled" size="16" color="#999" />
</view>
</uni-forms-item>
</view>
<!-- 活动设置区域 -->
<view class="form-section">
<view class="section-title">
<uni-icons type="gear" size="16" color="#ff5722" />
<text>活动设置</text>
</view>
<uni-forms-item label="活动类型" name="type" required>
<uni-data-select
v-model="formData.type"
:localdata="typeOptions"
placeholder="请选择活动类型" />
</uni-forms-item>
<uni-forms-item label="参与限制" name="participantLimit">
<view class="number-input-wrapper">
<uni-number-box
v-model="formData.participantLimit"
:min="0"
:max="9999"
placeholder="0" />
<text class="input-suffix">0表示无限制</text>
</view>
</uni-forms-item>
</view>
<!-- 详细规则区域 -->
<view class="form-section">
<view class="section-title">
<uni-icons type="list" size="16" color="#ff5722" />
<text>详细规则</text>
</view>
<uni-forms-item label="活动规则" name="rules">
<uni-easyinput
v-model="formData.rules"
type="textarea"
placeholder="请输入活动规则,如参与条件、活动流程等"
maxlength="500"
:auto-height="true" />
</uni-forms-item>
<uni-forms-item label="活动奖励" name="reward">
<uni-easyinput
v-model="formData.reward"
placeholder="请输入活动奖励,如优惠券、积分、礼品等"
maxlength="100"
:clearable="true" />
</uni-forms-item>
</view>
</uni-forms>
</view>
<!-- 操作按钮区域 -->
<view class="action-section">
<uni-button
class="action-btn draft-btn"
@click="saveDraft">
<uni-icons type="folder" size="18" color="#666" />
保存草稿
</uni-button>
<uni-button
class="action-btn publish-btn"
type="primary"
@click="submitForm">
<uni-icons type="paperplane" size="18" color="#fff" />
发布活动
</uni-button>
</view>
</view>
</view>
</template>
<script>
import CommonHeader from '@/components/common-header/common-header.vue'
import { createActivity } from '@/api/brewery/activity'
export default {
components: {
CommonHeader
},
data() {
return {
headerHeight: 96, // 默认头部高度
formData: {
title: '',
description: '',
startTime: '',
endTime: '',
type: '',
rules: '',
reward: '',
participantLimit: 0,
status: 0
},
typeOptions: [
{ value: '1', text: '限时抢购' },
{ value: '2', text: '满减活动' },
{ value: '3', text: '新人礼包' },
{ value: '4', text: '积分兑换' }
],
rules: {
title: {
rules: [
{ required: true, errorMessage: '请输入活动标题' },
{ minLength: 2, maxLength: 50, errorMessage: '标题长度在2-50字符之间' }
]
},
description: {
rules: [
{ maxLength: 200, errorMessage: '描述不能超过200字符' }
]
},
startTime: {
rules: [{ required: true, errorMessage: '请选择开始时间' }]
},
endTime: {
rules: [
{ required: true, errorMessage: '请选择结束时间' },
{
validateFunction: (rule, value, data) => {
if (data.startTime && value && new Date(value) <= new Date(data.startTime)) {
return '结束时间必须晚于开始时间'
}
return true
}
}
]
},
type: {
rules: [{ required: true, errorMessage: '请选择活动类型' }]
},
rules: {
rules: [
{ maxLength: 500, errorMessage: '规则不能超过500字符' }
]
},
reward: {
rules: [
{ maxLength: 100, errorMessage: '奖励描述不能超过100字符' }
]
}
}
}
},
computed: {
containerStyle() {
return {
paddingTop: this.headerHeight + 'px'
}
}
},
onLoad() {
this.listenHeaderHeight()
this.calculateHeaderHeight()
},
onUnload() {
uni.$off('header-height-updated', this.onHeaderHeightUpdated)
},
methods: {
goBack() {
uni.navigateBack()
},
async submitForm() {
try {
await this.$refs.form.validate()
this.formData.status = 1
await this.saveActivity()
} catch (error) {
console.log('表单验证失败', error)
}
},
async saveDraft() {
this.formData.status = 0
await this.saveActivity()
},
async saveActivity() {
try {
await createActivity(this.formData)
this.$modal.showToast('保存成功')
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
this.$modal.showToast('保存失败')
}
},
// 监听头部高度变化
listenHeaderHeight() {
uni.$on('header-height-updated', this.onHeaderHeightUpdated)
},
// 头部高度更新处理
onHeaderHeightUpdated(data) {
this.headerHeight = data.headerHeight
},
// 直接计算头部高度
calculateHeaderHeight() {
try {
const systemInfo = uni.getSystemInfoSync()
const statusBarHeight = systemInfo.statusBarHeight || 0
let navBarHeight = 44
// #ifdef MP-WEIXIN
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect()
if (menuButtonInfo) {
const topGap = menuButtonInfo.top - statusBarHeight
const bottomGap = topGap
navBarHeight = topGap + menuButtonInfo.height + bottomGap
}
} catch (e) {
console.warn('获取胶囊按钮信息失败:', e)
}
// #endif
// 确保最小高度
if (navBarHeight < 44) {
navBarHeight = 44
}
this.headerHeight = statusBarHeight + navBarHeight
} catch (e) {
console.warn('计算头部高度失败:', e)
this.headerHeight = 96 // 使用默认值
}
}
}
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: linear-gradient(180deg, #f8f9fa 0%, #f5f7fa 100%);
box-sizing: border-box;
// 头部间距将通过动态样式设置
// 如果动态样式不生效,使用默认值
padding-top: 96px;
}
.add-content {
padding: 0 20rpx 40rpx;
}
.form-card {
background: #fff;
border-radius: 24rpx;
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.08);
border: 1rpx solid #f0f0f0;
overflow: hidden;
}
.form-section {
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
.section-title {
display: flex;
align-items: center;
gap: 12rpx;
padding: 32rpx 32rpx 24rpx;
font-size: 32rpx;
font-weight: 600;
color: #1a1a1a;
background: linear-gradient(90deg, rgba(255, 87, 34, 0.05) 0%, rgba(255, 87, 34, 0.02) 100%);
border-bottom: 1rpx solid #f5f5f5;
text {
flex: 1;
}
}
}
// 表单项样式优化
::v-deep .uni-forms-item {
padding: 24rpx 32rpx;
border-bottom: 1rpx solid #f8f9fa;
&:last-child {
border-bottom: none;
}
.uni-forms-item__label {
font-size: 30rpx;
font-weight: 500;
color: #333;
}
.uni-forms-item__content {
.uni-easyinput__content {
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 16rpx;
font-size: 28rpx;
transition: all 0.3s ease;
&:focus-within {
border-color: #ff5722;
box-shadow: 0 0 0 3rpx rgba(255, 87, 34, 0.1);
}
}
.uni-datetime-picker {
border: 1rpx solid #e5e7eb;
border-radius: 12rpx;
padding: 16rpx;
font-size: 28rpx;
transition: all 0.3s ease;
&:focus-within {
border-color: #ff5722;
box-shadow: 0 0 0 3rpx rgba(255, 87, 34, 0.1);
}
}
}
}
.datetime-wrapper {
position: relative;
display: flex;
align-items: center;
.uni-datetime-picker {
flex: 1;
}
.uni-icons {
position: absolute;
right: 16rpx;
pointer-events: none;
}
}
.number-input-wrapper {
display: flex;
align-items: center;
gap: 16rpx;
.uni-number-box {
flex: 1;
}
.input-suffix {
font-size: 26rpx;
color: #666;
white-space: nowrap;
}
}
.action-section {
display: flex;
gap: 24rpx;
margin-top: 32rpx;
padding: 0 20rpx;
.action-btn {
flex: 1;
height: 88rpx;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
transition: all 0.3s ease;
border: none;
&.draft-btn {
background: #f8f9fa;
color: #666;
border: 2rpx solid #e9ecef;
&:active {
background: #e9ecef;
transform: scale(0.98);
}
}
&.publish-btn {
background: linear-gradient(45deg, #ff5722, #ff7043);
color: #fff;
box-shadow: 0 8rpx 24rpx rgba(255, 87, 34, 0.4);
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 16rpx rgba(255, 87, 34, 0.5);
}
}
}
}
// 响应式适配
@media (max-width: 750rpx) {
.form-section .section-title {
padding: 24rpx 24rpx 20rpx;
font-size: 30rpx;
}
::v-deep .uni-forms-item {
padding: 20rpx 24rpx;
}
.action-section {
padding: 0 16rpx;
gap: 20rpx;
.action-btn {
height: 80rpx;
font-size: 30rpx;
}
}
}
</style>