/** * 申诉提交页(重构:用 appealService.submitAppeal + ui-nav-bar + ui-textarea) * * 用户针对封禁/暂停处置提交申诉。类型由入口决定(ban_appeal / suspend_appeal)。 * * 不变量(AGENTS.md): * - 第 8 条:敏感数据默认拒绝——本页只接收申诉原因 + 证据截图 + 联系方式, * 不接受身份证号等敏感字段;联系方式为可选。 * - 第 4 条:Block 优先级最高——申诉不会自动解封或解除拉黑,由运营人工处置。 * 幂等:appealService.submitAppeal 内部以 appealType+relatedActionId+秒级时间生成 Idempotency-Key。 */ const { appStore } = require('../../stores'); const { appealService } = require('../../services'); const TYPE_LABELS = { ban_appeal: '封禁申诉', suspend_appeal: '暂停申诉', }; const MIN_REASON_LEN = 10; const MAX_EVIDENCE = 5; Page({ data: { appealType: '', actionId: '', typeLabel: '申诉', reason: '', reasonError: '', evidenceFiles: [], contactMethod: '', submitting: false, submitted: false, statusBarHeight: 20, navBarHeight: 44, }, onLoad(options) { try { const info = (wx.getWindowInfo && wx.getWindowInfo()) || wx.getSystemInfoSync(); this.setData({ statusBarHeight: info.statusBarHeight || 20, navBarHeight: info.platform === 'android' ? 48 : 44, }); } catch (e) { /* 使用默认值 */ } const appealType = options.type ? decodeURIComponent(options.type) : ''; const actionId = options.actionId ? decodeURIComponent(options.actionId) : ''; this.setData({ appealType, actionId, typeLabel: TYPE_LABELS[appealType] || '申诉', }); }, onReasonInput(e) { this.setData({ reason: e.detail.value, reasonError: '' }); }, onContactInput(e) { this.setData({ contactMethod: e.detail.value }); }, // ============ 证据上传 ============ chooseEvidence() { const remain = MAX_EVIDENCE - this.data.evidenceFiles.length; if (remain <= 0) { appStore.toast('最多上传 5 张证据'); return; } wx.chooseMedia({ count: remain, mediaType: ['image'], sourceType: ['album', 'camera'], success: (res) => { const files = res.tempFiles.map((f) => f.tempFilePath); this.setData({ evidenceFiles: [...this.data.evidenceFiles, ...files] }); }, }); }, previewImage(e) { wx.previewImage({ current: e.currentTarget.dataset.url, urls: this.data.evidenceFiles, }); }, removeImage(e) { const idx = e.currentTarget.dataset.index; const files = this.data.evidenceFiles; files.splice(idx, 1); this.setData({ evidenceFiles: files }); }, // ============ 提交 ============ async submitAppeal() { const { appealType, actionId, reason, contactMethod } = this.data; const trimmedReason = (reason || '').trim(); if (!appealType) { appStore.toast('缺少申诉类型'); return; } if (!trimmedReason) { this.setData({ reasonError: '请填写申诉原因' }); return; } if (trimmedReason.length < MIN_REASON_LEN) { this.setData({ reasonError: `申诉原因请至少 ${MIN_REASON_LEN} 个字` }); return; } this.setData({ submitting: true }); try { await appealService.submitAppeal({ appealType, relatedActionId: actionId || undefined, reason: trimmedReason, // 证据上传至 OSS 后回填 fileId;当前阶段先以本地路径占位,由后端证据服务接入后补全 evidenceFileIds: [], contactMethod: (contactMethod || '').trim() || undefined, }); this.setData({ submitted: true, submitting: false }); appStore.toastSuccess('申诉已提交'); } catch (err) { appStore.toastError(err); this.setData({ submitting: false }); } }, goBack() { wx.navigateBack(); }, onBack() { wx.navigateBack(); }, });