You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

709 lines
18 KiB

2 weeks ago
const app = getApp<IAppOption>()
const DRAFT_KEY = 'actAdd:draft'
interface ILevelItem {
id: number
name: string
code: string
sort: number
}
interface ICategoryItem {
id: number
name: string
code: string
icon: string
sort: number
isEnabled: number
}
interface ITagItem {
2 weeks ago
id: number
name: string
sort: number
activityCount: number
}
interface AgendaItem {
agendaDate: string
agendaTime: string
2 weeks ago
title: string
description: string
sort: number
2 weeks ago
}
2 weeks ago
Page({
2 weeks ago
data: {
currentStep: 1,
steps: [
{ label: '基本信息', field: 'basic' },
{ label: '报名签到设置', field: 'signup' },
{ label: '活动议程', field: 'agenda' },
],
// 步骤1 基本信息
coverImageList: [] as Array<{ uid: string; url: string; type: string; name: string; size: number }>,
2 weeks ago
title: '',
type: 1,
typeOptions: [
{ id: 1, name: '讲座' },
{ id: 2, name: '比赛' },
{ id: 3, name: '社团' },
{ id: 4, name: '志愿' },
{ id: 5, name: '文体' },
{ id: 6, name: '其他' },
],
typeOther: '',
summary: '',
2 weeks ago
startTime: '',
endTime: '',
detail: '',
detailImages: [] as string[],
2 weeks ago
location: '',
organizer: '',
contactName: '',
contactPhone: '',
// 活动等级
levelList: [] as ILevelItem[],
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 }>, // 用于显示选中状态
2 weeks ago
// 步骤2 报名签到设置
2 weeks ago
needRegister: true,
registerStartTime: '',
registerEndTime: '',
registerLimit: 'unlimited',
registerLimitCount: '',
registerCondition: '',
checkinWay: 'dynamic',
checkinStartTime: '',
checkinEndTime: '',
// 步骤3 活动议程
agendas: [{ agendaDate: '', agendaTime: '', title: '', description: '', sort: 0 }] as AgendaItem[],
nextAgendaId: 2,
// 提交状态
submitting: false,
2 weeks ago
},
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 })
},
setAndSave(patch: Record<string, any>) {
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)
}
2 weeks ago
},
// ========== 步骤切换 ==========
goStep(step: number) {
if (step < 1 || step > 4) return
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
2 weeks ago
},
onNextStep() {
// 先验证当前步骤
if (!this.validateCurrentStep()) {
return
}
2 weeks ago
const next = this.data.currentStep + 1
if (next <= 4) this.setAndSave({ currentStep: next })
2 weeks ago
},
onPrevStep() {
const prev = this.data.currentStep - 1
if (prev >= 1) this.setAndSave({ currentStep: prev })
2 weeks ago
},
// ========== 图片上传 ==========
// 上传成功后,直接添加到列表(maxCount=1,只保留一个)
onCoverSuccess(e: WechatMiniprogram.CustomEvent) {
const { file } = e.detail
this.setAndSave({ coverImageList: [file] })
},
// 上传失败后,显示错误
onCoverError(_e: WechatMiniprogram.CustomEvent) {
wx.showToast({ title: '上传失败,请重试', icon: 'none' })
},
// 删除封面图片
handleDelCover() {
this.setAndSave({ coverImageList: [] })
},
onDetailImageSuccess(e: WechatMiniprogram.CustomEvent) {
const { urls } = e.detail
this.setAndSave({ detailImages: urls })
2 weeks ago
},
// ========== 输入绑定 ==========
onInputChange(e: WechatMiniprogram.Input) {
const { field } = e.currentTarget.dataset
this.setAndSave({ [field]: e.detail.value })
2 weeks ago
},
onTextareaChange(e: WechatMiniprogram.TextareaInput) {
const { field } = e.currentTarget.dataset
this.setAndSave({ [field]: e.detail.value })
2 weeks ago
},
// ========== 时间选择 ==========
onPickTime(e: WechatMiniprogram.PickerChange) {
const { field } = e.currentTarget.dataset
this.setAndSave({ [field]: e.detail.value })
2 weeks ago
},
// ========== 活动类型选择 ==========
onSelectType(e: WechatMiniprogram.TouchEvent) {
2 weeks ago
const { value } = e.currentTarget.dataset
this.setAndSave({ type: value })
},
// ========== 活动等级选择 ==========
onSelectLevel(e: WechatMiniprogram.TouchEvent) {
const { id } = e.currentTarget.dataset
this.setAndSave({ levelId: id })
2 weeks ago
},
// ========== 活动分类选择(多选) ==========
2 weeks ago
onSelectCategory(e: WechatMiniprogram.TouchEvent) {
const { id } = e.currentTarget.dataset
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 })
2 weeks ago
},
// ========== 议程管理 ==========
onAddAgenda() {
const agendas = this.data.agendas
agendas.push({
agendaDate: '',
agendaTime: '',
2 weeks ago
title: '',
description: '',
sort: agendas.length,
2 weeks ago
})
this.setAndSave({ agendas })
2 weeks ago
},
onRemoveAgenda(e: WechatMiniprogram.TouchEvent) {
const { index } = e.currentTarget.dataset
const agendas = this.data.agendas.filter((_, i) => i !== index)
this.setAndSave({ agendas })
2 weeks ago
},
onAgendaInput(e: WechatMiniprogram.Input | WechatMiniprogram.TextareaInput) {
const { index, field } = e.currentTarget.dataset
const agendas = this.data.agendas
agendas[index][field] = e.detail.value
this.setAndSave({ agendas })
2 weeks ago
},
onAgendaTime(e: WechatMiniprogram.PickerChange) {
const { index, field } = e.currentTarget.dataset
2 weeks ago
const agendas = this.data.agendas
agendas[index][field] = e.detail.value as string
this.setAndSave({ agendas })
2 weeks ago
},
// ========== 报名签到设置 ==========
onToggleRegister(e: WechatMiniprogram.TouchEvent) {
const { value } = e.currentTarget.dataset
this.setAndSave({ needRegister: value === 'yes' })
2 weeks ago
},
onToggleRegisterLimit(e: WechatMiniprogram.TouchEvent) {
const { value } = e.currentTarget.dataset
this.setAndSave({ registerLimit: value })
2 weeks ago
},
onSelectCheckinWay(e: WechatMiniprogram.TouchEvent) {
const { value } = e.currentTarget.dataset
this.setAndSave({ checkinWay: value })
2 weeks ago
},
// ========== 底部操作 ==========
onSaveDraft() {
this.submitActivity(1) // activityStatus = 1 (草稿)
2 weeks ago
},
onSubmit() {
wx.showModal({
title: '确认提交',
content: '提交后将进入审核流程,是否继续?',
success: (res) => {
if (res.confirm) {
this.submitActivity(2) // activityStatus = 2 (待审核)
2 weeks ago
}
},
})
},
// 提交活动申请
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 })
}
},
2 weeks ago
onGoHome() {
this.clearDraft()
2 weeks ago
wx.switchTab({ url: '/pages/index/index' })
},
})
2 weeks ago
export {}