const app = getApp() 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 { id: number name: string sort: number activityCount: number } interface AgendaItem { agendaDate: string agendaTime: string title: string description: string sort: number } Page({ 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 }>, 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: '', endTime: '', detail: '', detailImages: [] as string[], 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 报名签到设置 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, }, 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 = {}) { 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 = {} 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 = {}) { 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) { 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) { 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) { 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 }, onNextStep() { // 先验证当前步骤 if (!this.validateCurrentStep()) { return } const next = this.data.currentStep + 1 if (next <= 4) this.setAndSave({ currentStep: next }) }, onPrevStep() { const prev = this.data.currentStep - 1 if (prev >= 1) this.setAndSave({ currentStep: prev }) }, // ========== 图片上传 ========== // 上传成功后,直接添加到列表(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 }) }, // ========== 输入绑定 ========== onInputChange(e: WechatMiniprogram.Input) { const { field } = e.currentTarget.dataset this.setAndSave({ [field]: e.detail.value }) }, onTextareaChange(e: WechatMiniprogram.TextareaInput) { const { field } = e.currentTarget.dataset this.setAndSave({ [field]: e.detail.value }) }, // ========== 时间选择 ========== onPickTime(e: WechatMiniprogram.PickerChange) { const { field } = e.currentTarget.dataset this.setAndSave({ [field]: e.detail.value }) }, // ========== 活动类型选择 ========== onSelectType(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset this.setAndSave({ type: value }) }, // ========== 活动等级选择 ========== onSelectLevel(e: WechatMiniprogram.TouchEvent) { const { id } = e.currentTarget.dataset this.setAndSave({ levelId: id }) }, // ========== 活动分类选择(多选) ========== 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 }) }, // ========== 议程管理 ========== onAddAgenda() { const agendas = this.data.agendas agendas.push({ agendaDate: '', agendaTime: '', title: '', description: '', sort: agendas.length, }) this.setAndSave({ agendas }) }, onRemoveAgenda(e: WechatMiniprogram.TouchEvent) { const { index } = e.currentTarget.dataset const agendas = this.data.agendas.filter((_, i) => i !== index) this.setAndSave({ agendas }) }, 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 }) }, onAgendaTime(e: WechatMiniprogram.PickerChange) { const { index, field } = e.currentTarget.dataset const agendas = this.data.agendas agendas[index][field] = e.detail.value as string this.setAndSave({ agendas }) }, // ========== 报名签到设置 ========== onToggleRegister(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset this.setAndSave({ needRegister: value === 'yes' }) }, onToggleRegisterLimit(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset this.setAndSave({ registerLimit: value }) }, onSelectCheckinWay(e: WechatMiniprogram.TouchEvent) { const { value } = e.currentTarget.dataset this.setAndSave({ checkinWay: value }) }, // ========== 底部操作 ========== onSaveDraft() { this.submitActivity(1) // activityStatus = 1 (草稿) }, onSubmit() { wx.showModal({ title: '确认提交', content: '提交后将进入审核流程,是否继续?', success: (res) => { if (res.confirm) { 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 = { dynamic: 1, fixed: 2, none: 3, } const params: Record = { 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() { this.clearDraft() wx.switchTab({ url: '/pages/index/index' }) }, }) export {}