@ -1,10 +1,36 @@
const app = getApp < IAppOption > ( )
const app = getApp < IAppOption > ( )
interface AgendaItem {
const DRAFT_KEY = 'actAdd:draft'
interface ILevelItem {
id : number
name : string
code : string
sort : number
}
interface ICategoryItem {
id : number
id : number
time : string
name : string
code : string
icon : string
sort : number
isEnabled : number
}
interface ITagItem {
id : number
name : string
sort : number
activityCount : number
}
interface AgendaItem {
agendaDate : string
agendaTime : string
title : string
title : string
desc : string
description : string
sort : number
}
}
Page ( {
Page ( {
@ -17,25 +43,43 @@ Page({
] ,
] ,
// 步骤1 基本信息
// 步骤1 基本信息
coverImageList : [ ] as Array < { uid : string , url : string , type : string , name : string , size : number } > ,
coverImageList : [ ] as Array < { uid : string ; url : string ; type : string ; name : string ; size : number } > ,
title : '' ,
title : '' ,
type : 1 ,
typeOptions : [
{ id : 1 , name : '讲座' } ,
{ id : 2 , name : '比赛' } ,
{ id : 3 , name : '社团' } ,
{ id : 4 , name : '志愿' } ,
{ id : 5 , name : '文体' } ,
{ id : 6 , name : '其他' } ,
] ,
typeOther : '' ,
summary : '' ,
startTime : '' ,
startTime : '' ,
endTime : '' ,
endTime : '' ,
detail : '' ,
detail : '' ,
level : '' ,
detailImages : [ ] as string [ ] ,
levels : [ '校级' , '院级' , '系级' , '班级' , '其他' ] ,
category : '' ,
categories : [ '讲座' , '比赛' , '社团活动' , '志愿服务' , '文体活动' , '学术交流' , '其他' ] ,
location : '' ,
location : '' ,
organizer : '' ,
organizer : '' ,
contactName : '' ,
contactName : '' ,
contactPhone : '' ,
contactPhone : '' ,
// 步骤2 活动议程
// 活动等级
agendas : [ { id : 1 , time : '' , title : '' , desc : '' } ] as AgendaItem [ ] ,
levelList : [ ] as ILevelItem [ ] ,
nextAgendaId : 2 ,
levelId : 0 ,
// 活动分类
categoryList : [ ] as ICategoryItem [ ] ,
selectedCategoryIds : [ ] as number [ ] ,
categoryTags : [ ] as Array < { id : number ; name : string ; isSelected : boolean } > , // 用于显示选中状态
// 活动标签
tagList : [ ] as ITagItem [ ] ,
selectedTagIds : [ ] as number [ ] ,
tagTags : [ ] as Array < { id : number ; name : string ; isSelected : boolean } > , // 用于显示选中状态
// 步骤3 报名签到设置
// 步骤2 报名签到设置
needRegister : true ,
needRegister : true ,
registerStartTime : '' ,
registerStartTime : '' ,
registerEndTime : '' ,
registerEndTime : '' ,
@ -46,120 +90,446 @@ Page({
checkinWay : 'dynamic' ,
checkinWay : 'dynamic' ,
checkinStartTime : '' ,
checkinStartTime : '' ,
checkinEndTime : '' ,
checkinEndTime : '' ,
// 步骤3 活动议程
agendas : [ { agendaDate : '' , agendaTime : '' , title : '' , description : '' , sort : 0 } ] as AgendaItem [ ] ,
nextAgendaId : 2 ,
// 提交状态
submitting : false ,
} ,
async onLoad() {
await app . waitLogin ( { type : 1 } )
try {
await Promise . all ( [ this . fetchLevelList ( ) , this . fetchCategoryList ( ) , this . fetchTagList ( ) ] )
} catch ( err ) {
console . error ( '初始化数据失败:' , err )
}
// 检查是否有本地草稿
try {
const draft = wx . getStorageSync ( DRAFT_KEY ) as any
if ( draft && Object . keys ( draft ) . length ) {
wx . showModal ( {
title : '检测到未完成的活动' ,
content : '上次未完成的表单已保存,是否继续编辑?' ,
confirmText : '继续编辑' ,
cancelText : '重新开始' ,
success : ( res ) = > {
if ( res . confirm ) {
this . restoreDraft ( draft )
} else {
// 用户选择重新开始,清除草稿
this . clearDraft ( )
}
} ,
} )
}
} catch ( err ) {
console . error ( '读取本地草稿失败:' , err )
}
} ,
// ========== 草稿管理 ==========
buildDraft ( partial : Record < string , any > = { } ) {
const keys = [
'currentStep' ,
'coverImageList' ,
'title' ,
'type' ,
'typeOther' ,
'summary' ,
'startTime' ,
'endTime' ,
'detail' ,
'detailImages' ,
'location' ,
'organizer' ,
'contactName' ,
'contactPhone' ,
'levelId' ,
'selectedCategoryIds' ,
'selectedTagIds' ,
'needRegister' ,
'registerStartTime' ,
'registerEndTime' ,
'registerLimit' ,
'registerLimitCount' ,
'registerCondition' ,
'checkinWay' ,
'checkinStartTime' ,
'checkinEndTime' ,
'agendas' ,
]
const draft : Record < string , any > = { }
keys . forEach ( ( k ) = > {
if ( Object . prototype . hasOwnProperty . call ( partial , k ) ) {
draft [ k ] = partial [ k ]
} else {
draft [ k ] = ( this . data as any ) [ k ]
}
} )
return draft
} ,
saveDraft ( partial : Record < string , any > = { } ) {
try {
const draft = this . buildDraft ( partial )
wx . setStorageSync ( DRAFT_KEY , draft )
} catch ( err ) {
console . error ( '保存草稿失败:' , err )
}
} ,
clearDraft() {
try {
wx . removeStorageSync ( DRAFT_KEY )
} catch {
/* ignore */
}
} ,
restoreDraft ( draft : Record < string , any > ) {
if ( ! draft ) return
const safeDraft = { . . . draft }
safeDraft . coverImageList = safeDraft . coverImageList || [ ]
safeDraft . detailImages = safeDraft . detailImages || [ ]
safeDraft . agendas = safeDraft . agendas || [ ]
safeDraft . selectedCategoryIds = safeDraft . selectedCategoryIds || [ ]
safeDraft . selectedTagIds = safeDraft . selectedTagIds || [ ]
this . setData ( safeDraft )
// 重建标签选中状态显示
const categoryTags = ( this . data . categoryList || [ ] ) . map ( ( item : ICategoryItem ) = > ( {
id : item.id ,
name : item.name ,
isSelected : safeDraft.selectedCategoryIds.includes ( item . id ) ,
} ) )
const tagTags = ( this . data . tagList || [ ] ) . map ( ( item : ITagItem ) = > ( {
id : item.id ,
name : item.name ,
isSelected : safeDraft.selectedTagIds.includes ( item . id ) ,
} ) )
this . setData ( { categoryTags , tagTags } )
} ,
} ,
onLoad() {
setAndSave ( patch : Record < string , any > ) {
app . waitLogin ( )
this . setData ( patch )
this . saveDraft ( patch )
} ,
// 获取活动等级列表
async fetchLevelList() {
try {
const res = await wx . ajax ( {
url : '/activity-level/list' ,
method : 'GET' ,
data : { } ,
} )
if ( res && res . list ) {
this . setData ( { levelList : res.list } )
}
} catch ( err ) {
console . error ( '获取活动等级列表失败:' , err )
}
} ,
// 获取活动分类列表
async fetchCategoryList() {
try {
const res = await wx . ajax ( {
url : '/activity-category/list' ,
method : 'GET' ,
data : { } ,
} )
if ( res && res . list ) {
const { selectedCategoryIds } = this . data
const categoryTags = res . list . map ( ( item : ICategoryItem ) = > ( {
id : item.id ,
name : item.name ,
isSelected : selectedCategoryIds.includes ( item . id ) ,
} ) )
this . setData ( {
categoryList : res.list ,
categoryTags ,
} )
}
} catch ( err ) {
console . error ( '获取活动分类列表失败:' , err )
}
} ,
// 获取活动标签列表
async fetchTagList() {
try {
const res = await wx . ajax ( {
url : '/activity-tag/list' ,
method : 'GET' ,
data : { } ,
} )
if ( res && res . list ) {
const { selectedTagIds } = this . data
const tagTags = res . list . map ( ( item : ITagItem ) = > ( {
id : item.id ,
name : item.name ,
isSelected : selectedTagIds.includes ( item . id ) ,
} ) )
this . setData ( {
tagList : res.list ,
tagTags ,
} )
}
} catch ( err ) {
console . error ( '获取活动标签列表失败:' , err )
}
} ,
} ,
// ========== 步骤切换 ==========
// ========== 步骤切换 ==========
goStep ( step : number ) {
goStep ( step : number ) {
if ( step < 1 || step > 4 ) return
if ( step < 1 || step > 4 ) return
this . setData ( { currentStep : step } )
this . setAndSave ( { currentStep : step } )
} ,
// 验证当前步骤的必填项
validateCurrentStep ( ) : boolean {
const {
currentStep ,
coverImageList ,
title ,
startTime ,
endTime ,
location ,
needRegister ,
registerStartTime ,
registerEndTime ,
checkinWay ,
checkinStartTime ,
checkinEndTime ,
agendas ,
} = this . data
// 步骤1:基本信息
if ( currentStep === 1 ) {
if ( ! coverImageList . length ) {
wx . showToast ( { title : '请上传活动头图' , icon : 'none' } )
return false
}
if ( ! title . trim ( ) ) {
wx . showToast ( { title : '请输入活动标题' , icon : 'none' } )
return false
}
if ( ! startTime ) {
wx . showToast ( { title : '请选择活动开始时间' , icon : 'none' } )
return false
}
if ( ! endTime ) {
wx . showToast ( { title : '请选择活动结束时间' , icon : 'none' } )
return false
}
if ( ! location . trim ( ) ) {
wx . showToast ( { title : '请输入活动地点' , icon : 'none' } )
return false
}
}
// 步骤2:报名签到设置
if ( currentStep === 2 ) {
if ( needRegister ) {
if ( ! registerStartTime ) {
wx . showToast ( { title : '请选择报名开始时间' , icon : 'none' } )
return false
}
if ( ! registerEndTime ) {
wx . showToast ( { title : '请选择报名截止时间' , icon : 'none' } )
return false
}
}
// 签到时间校验(动态二维码或固定二维码时必填)
if ( checkinWay !== 'none' ) {
if ( ! checkinStartTime ) {
wx . showToast ( { title : '请选择签到开始时间' , icon : 'none' } )
return false
}
if ( ! checkinEndTime ) {
wx . showToast ( { title : '请选择签到结束时间' , icon : 'none' } )
return false
}
}
}
// 步骤3:活动议程
if ( currentStep === 3 ) {
if ( ! agendas . length || ! agendas [ 0 ] . title . trim ( ) ) {
wx . showToast ( { title : '请添加活动议程' , icon : 'none' } )
return false
}
}
return true
} ,
} ,
onNextStep() {
onNextStep() {
// 先验证当前步骤
if ( ! this . validateCurrentStep ( ) ) {
return
}
const next = this . data . currentStep + 1
const next = this . data . currentStep + 1
if ( next <= 4 ) this . setData ( { currentStep : next } )
if ( next <= 4 ) this . setAndSave ( { currentStep : next } )
} ,
} ,
onPrevStep() {
onPrevStep() {
const prev = this . data . currentStep - 1
const prev = this . data . currentStep - 1
if ( prev >= 1 ) this . setData ( { currentStep : prev } )
if ( prev >= 1 ) this . setAndSave ( { currentStep : prev } )
} ,
} ,
// ========== 图片上传 ==========
// ========== 图片上传 ==========
onCoverUploadSuccess ( e : WechatMiniprogram.CustomEvent ) {
// 上传成功后,直接添加到列表(maxCount=1,只保留一个)
onCoverSuccess ( e : WechatMiniprogram.CustomEvent ) {
const { file } = e . detail
const { file } = e . detail
this . setData ( { coverImageList : [ file ] } )
this . setAndSave ( { coverImageList : [ file ] } )
} ,
// 上传失败后,显示错误
onCoverError ( _e : WechatMiniprogram.CustomEvent ) {
wx . showToast ( { title : '上传失败,请重试' , icon : 'none' } )
} ,
// 删除封面图片
handleDelCover() {
this . setAndSave ( { coverImageList : [ ] } )
} ,
} ,
onCoverRemove ( _e : WechatMiniprogram.CustomEvent ) {
onDetailImageSuccess ( e : WechatMiniprogram.CustomEvent ) {
this . setData ( { coverImageList : [ ] } )
const { urls } = e . detail
this . setAndSave ( { detailImages : urls } )
} ,
} ,
// ========== 输入绑定 ==========
// ========== 输入绑定 ==========
onInputChange ( e : WechatMiniprogram.Input ) {
onInputChange ( e : WechatMiniprogram.Input ) {
const { field } = e . currentTarget . dataset
const { field } = e . currentTarget . dataset
this . setData ( { [ field ] : e . detail . value } )
this . setAndSave ( { [ field ] : e . detail . value } )
} ,
} ,
onTextareaChange ( e : WechatMiniprogram.TextareaInput ) {
onTextareaChange ( e : WechatMiniprogram.TextareaInput ) {
const { field } = e . currentTarget . dataset
const { field } = e . currentTarget . dataset
this . setData ( { [ field ] : e . detail . value } )
this . setAndSave ( { [ field ] : e . detail . value } )
} ,
} ,
// ========== 时间选择 ==========
// ========== 时间选择 ==========
onPickTime ( e : WechatMiniprogram.PickerChange ) {
onPickTime ( e : WechatMiniprogram.PickerChange ) {
const { field } = e . currentTarget . dataset
const { field } = e . currentTarget . dataset
this . setData ( { [ field ] : e . detail . value } )
this . setAndSave ( { [ field ] : e . detail . value } )
} ,
} ,
// ========== 标签选择 ==========
// ========== 活动类型 选择 ==========
onSelectLevel ( e : WechatMiniprogram.TouchEvent ) {
onSelectType ( e : WechatMiniprogram.TouchEvent ) {
const { value } = e . currentTarget . dataset
const { value } = e . currentTarget . dataset
this . setData ( { level : value } )
this . setAndSave ( { type : value } )
} ,
} ,
// ========== 活动等级选择 ==========
onSelectLevel ( e : WechatMiniprogram.TouchEvent ) {
const { id } = e . currentTarget . dataset
this . setAndSave ( { levelId : id } )
} ,
// ========== 活动分类选择(多选) ==========
onSelectCategory ( e : WechatMiniprogram.TouchEvent ) {
onSelectCategory ( e : WechatMiniprogram.TouchEvent ) {
const { value } = e . currentTarget . dataset
const { id } = e . currentTarget . dataset
this . setData ( { category : value } )
let { selectedCategoryIds , categoryTags } = this . data
const index = selectedCategoryIds . indexOf ( id )
if ( index > - 1 ) {
selectedCategoryIds = selectedCategoryIds . filter ( ( item ) = > item !== id )
} else {
selectedCategoryIds = [ . . . selectedCategoryIds , id ]
}
categoryTags = categoryTags . map ( ( item ) = > ( {
. . . item ,
isSelected : selectedCategoryIds.includes ( item . id ) ,
} ) )
this . setAndSave ( { selectedCategoryIds , categoryTags } )
} ,
// ========== 活动标签选择(多选) ==========
onSelectTag ( e : WechatMiniprogram.TouchEvent ) {
const { id } = e . currentTarget . dataset
let { selectedTagIds , tagTags } = this . data
const index = selectedTagIds . indexOf ( id )
if ( index > - 1 ) {
selectedTagIds = selectedTagIds . filter ( ( item ) = > item !== id )
} else {
selectedTagIds = [ . . . selectedTagIds , id ]
}
tagTags = tagTags . map ( ( item ) = > ( {
. . . item ,
isSelected : selectedTagIds.includes ( item . id ) ,
} ) )
this . setAndSave ( { selectedTagIds , tagTags } )
} ,
} ,
// ========== 议程管理 ==========
// ========== 议程管理 ==========
onAddAgenda() {
onAddAgenda() {
const agendas = this . data . agendas
const agendas = this . data . agendas
agendas . push ( {
agendas . push ( {
id : this.data.nextAgendaId ,
agendaDate : '' ,
time : '' ,
agendaT ime: '' ,
title : '' ,
title : '' ,
desc : '' ,
description : '' ,
} )
sort : agendas.length ,
this . setData ( {
agendas ,
nextAgendaId : this.data.nextAgendaId + 1 ,
} )
} )
this . setAndSave ( { agendas } )
} ,
} ,
onRemoveAgenda ( e : WechatMiniprogram.TouchEvent ) {
onRemoveAgenda ( e : WechatMiniprogram.TouchEvent ) {
const { index } = e . currentTarget . dataset
const { index } = e . currentTarget . dataset
const agendas = this . data . agendas . filter ( ( _ , i ) = > i !== index )
const agendas = this . data . agendas . filter ( ( _ , i ) = > i !== index )
this . setData ( { agendas } )
this . setAndSave ( { agendas } )
} ,
} ,
onAgendaInput ( e : WechatMiniprogram.Input | WechatMiniprogram . TextareaInput ) {
onAgendaInput ( e : WechatMiniprogram.Input | WechatMiniprogram . TextareaInput ) {
const { index , field } = e . currentTarget . dataset
const { index , field } = e . currentTarget . dataset
const agendas = this . data . agendas
const agendas = this . data . agendas
agendas [ index ] [ field ] = e . detail . value
agendas [ index ] [ field ] = e . detail . value
this . setData ( { agendas } )
this . setAndSave ( { agendas } )
} ,
} ,
onAgendaTime ( e : WechatMiniprogram.PickerChange ) {
onAgendaTime ( e : WechatMiniprogram.PickerChange ) {
const { index } = e . currentTarget . dataset
const { index , field } = e . currentTarget . dataset
const agendas = this . data . agendas
const agendas = this . data . agendas
agendas [ index ] . time = e . detail . value as string
agendas [ index ] [ field ] = e . detail . value as string
this . setData ( { agendas } )
this . setAndSave ( { agendas } )
} ,
} ,
// ========== 报名签到设置 ==========
// ========== 报名签到设置 ==========
onToggleRegister ( e : WechatMiniprogram.TouchEvent ) {
onToggleRegister ( e : WechatMiniprogram.TouchEvent ) {
const { value } = e . currentTarget . dataset
const { value } = e . currentTarget . dataset
this . setData ( { needRegister : value === 'yes' } )
this . setAndSave ( { needRegister : value === 'yes' } )
} ,
} ,
onToggleRegisterLimit ( e : WechatMiniprogram.TouchEvent ) {
onToggleRegisterLimit ( e : WechatMiniprogram.TouchEvent ) {
const { value } = e . currentTarget . dataset
const { value } = e . currentTarget . dataset
this . setData ( { registerLimit : value } )
this . setAndSave ( { registerLimit : value } )
} ,
} ,
onSelectCheckinWay ( e : WechatMiniprogram.TouchEvent ) {
onSelectCheckinWay ( e : WechatMiniprogram.TouchEvent ) {
const { value } = e . currentTarget . dataset
const { value } = e . currentTarget . dataset
this . setData ( { checkinWay : value } )
this . setAndSave ( { checkinWay : value } )
} ,
} ,
// ========== 底部操作 ==========
// ========== 底部操作 ==========
onSaveDraft() {
onSaveDraft() {
wx . showToast ( { title : '已保存草稿' , icon : 'success' } )
this . submitActivity ( 1 ) // activityStatus = 1 (草稿)
} ,
} ,
onSubmit() {
onSubmit() {
@ -168,13 +538,169 @@ Page({
content : '提交后将进入审核流程,是否继续?' ,
content : '提交后将进入审核流程,是否继续?' ,
success : ( res ) = > {
success : ( res ) = > {
if ( res . confirm ) {
if ( res . confirm ) {
this . setData ( { currentStep : 4 } )
this . submitActivity ( 2 ) // activityStatus = 2 (待审核)
}
}
} ,
} ,
} )
} )
} ,
} ,
// 提交活动申请
async submitActivity ( activityStatus : number ) {
const {
coverImageList ,
title ,
type ,
typeOther ,
summary ,
startTime ,
endTime ,
location ,
organizer ,
contactName ,
contactPhone ,
levelId ,
selectedCategoryIds ,
selectedTagIds ,
needRegister ,
registerStartTime ,
registerEndTime ,
registerLimit ,
registerLimitCount ,
registerCondition ,
checkinWay ,
checkinStartTime ,
checkinEndTime ,
agendas ,
detailImages ,
submitting ,
} = this . data
if ( submitting ) return
// 校验必填字段
if ( ! coverImageList . length ) {
wx . showToast ( { title : '请上传活动头图' , icon : 'error' } )
return
}
if ( ! title . trim ( ) ) {
wx . showToast ( { title : '请输入活动标题' , icon : 'error' } )
return
}
if ( ! startTime ) {
wx . showToast ( { title : '请选择活动开始时间' , icon : 'error' } )
return
}
if ( ! endTime ) {
wx . showToast ( { title : '请选择活动结束时间' , icon : 'error' } )
return
}
if ( ! location . trim ( ) ) {
wx . showToast ( { title : '请输入活动地点' , icon : 'error' } )
return
}
// 校验报名设置
if ( needRegister ) {
if ( ! registerStartTime ) {
wx . showToast ( { title : '请选择报名开始时间' , icon : 'error' } )
return
}
if ( ! registerEndTime ) {
wx . showToast ( { title : '请选择报名截止时间' , icon : 'error' } )
return
}
}
// 校验签到设置
if ( checkinWay !== 'none' ) {
if ( ! checkinStartTime ) {
wx . showToast ( { title : '请选择签到开始时间' , icon : 'error' } )
return
}
if ( ! checkinEndTime ) {
wx . showToast ( { title : '请选择签到结束时间' , icon : 'error' } )
return
}
}
// 校验议程
if ( ! agendas . length || ! agendas [ 0 ] . title . trim ( ) ) {
wx . showToast ( { title : '请添加活动议程' , icon : 'error' } )
return
}
this . setData ( { submitting : true } )
wx . showLoading ( { title : activityStatus === 1 ? '保存中...' : '提交中...' } )
try {
const checkinTypeMap : Record < string , number > = {
dynamic : 1 ,
fixed : 2 ,
none : 3 ,
}
const params : Record < string , any > = {
mainImages : coverImageList.map ( ( item ) = > item . url ) ,
name : title ,
type ,
typeOther : type === 6 ? typeOther : '' ,
summary ,
detailImages ,
checkinType : checkinTypeMap [ checkinWay ] ,
regType : needRegister ? 1 : 2 ,
regCondition : registerCondition ,
quota : registerLimit === 'limited' ? Number ( registerLimitCount ) : 0 ,
regStartAt : needRegister ? registerStartTime : '' ,
regEndAt : needRegister ? registerEndTime : '' ,
startAt : startTime ,
endAt : endTime ,
location ,
organizer ,
contactName ,
contactPhone ,
tagIds : selectedTagIds ,
categoryIds : selectedCategoryIds ,
levelId ,
agendas : agendas.map ( ( item , index ) = > ( {
. . . item ,
sort : index ,
} ) ) ,
activityStatus ,
}
const res = await wx . ajax ( {
url : '/activity/apply' ,
method : 'POST' ,
data : params ,
} )
wx . hideLoading ( )
if ( res ) {
wx . showToast ( {
title : activityStatus === 1 ? '已保存草稿' : '提交成功' ,
icon : 'success' ,
} )
// 清理本地草稿
this . clearDraft ( )
// 跳转到结果页面
wx . redirectTo ( {
url : ` /pages/actAddResult/index?id= ${ res . activityId } &status= ${ res . status } ` ,
} )
}
} catch ( err : any ) {
wx . hideLoading ( )
const message = err ? . message || '提交失败'
wx . showToast ( { title : message , icon : 'error' } )
} finally {
this . setData ( { submitting : false } )
}
} ,
onGoHome() {
onGoHome() {
this . clearDraft ( )
wx . switchTab ( { url : '/pages/index/index' } )
wx . switchTab ( { url : '/pages/index/index' } )
} ,
} ,
} )
} )