feat: 申诉常见理由快捷选择 + 证据截图上传
- 后端: 新增 appeal_reason_type, appeal_evidence_urls 字段 - 后端: 新建 upload_routes.py 图片上传接口 - 前端: history 页面添加快捷理由选择器 + 截图上传 - 前端: admin-review 页面展示证据图片 + 点击预览 - 新增 SQL 更新脚本 update_appeal_fields.sql Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
const { request } = require('../../utils/request')
|
||||
const { request, uploadFile } = require('../../utils/request')
|
||||
|
||||
const STATUS_OPTIONS = [
|
||||
{ value: '', label: '全部状态' },
|
||||
@@ -26,6 +26,14 @@ const APPEAL_STATUS_TEXT = {
|
||||
rejected: '已驳回'
|
||||
}
|
||||
|
||||
const REASON_TYPE_OPTIONS = [
|
||||
{ value: '', label: '请选择申诉理由类型' },
|
||||
{ value: '正常活动文案', label: '正常活动文案' },
|
||||
{ value: '正常社区通知', label: '正常社区通知' },
|
||||
{ value: '私信沟通内容', label: '私信沟通内容' },
|
||||
{ value: '其他', label: '其他(需手动填写)' }
|
||||
]
|
||||
|
||||
Page({
|
||||
data: {
|
||||
loading: false,
|
||||
@@ -35,7 +43,12 @@ Page({
|
||||
visibilityOptions: VISIBILITY_OPTIONS,
|
||||
visibilityIndex: 0,
|
||||
appealPostId: null,
|
||||
appealReason: ''
|
||||
appealReasonType: '',
|
||||
appealReasonTypeOptions: REASON_TYPE_OPTIONS,
|
||||
appealReasonTypeIndex: 0,
|
||||
appealReason: '',
|
||||
appealEvidenceUrls: [],
|
||||
appealEvidenceFiles: []
|
||||
},
|
||||
|
||||
formatPercent(value, digits = 2) {
|
||||
@@ -84,33 +97,112 @@ Page({
|
||||
|
||||
startAppeal(e) {
|
||||
const postId = Number(e.currentTarget.dataset.id)
|
||||
this.setData({ appealPostId: postId, appealReason: '' })
|
||||
this.setData({
|
||||
appealPostId: postId,
|
||||
appealReasonType: '',
|
||||
appealReasonTypeIndex: 0,
|
||||
appealReason: '',
|
||||
appealEvidenceUrls: [],
|
||||
appealEvidenceFiles: []
|
||||
})
|
||||
},
|
||||
|
||||
onReasonTypeChange(e) {
|
||||
const idx = Number(e.detail.value || 0)
|
||||
const reasonType = this.data.appealReasonTypeOptions[idx].value
|
||||
this.setData({ appealReasonTypeIndex: idx, appealReasonType: reasonType })
|
||||
// 如果选择"其他",清空快捷理由,让用户手动输入
|
||||
if (reasonType === '其他') {
|
||||
this.setData({ appealReasonType: '' })
|
||||
}
|
||||
},
|
||||
|
||||
onAppealInput(e) {
|
||||
this.setData({ appealReason: e.detail.value || '' })
|
||||
},
|
||||
|
||||
chooseEvidence() {
|
||||
wx.chooseMedia({
|
||||
count: 3,
|
||||
mediaType: ['image'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const files = res.tempFiles.map((f) => f.tempFilePath)
|
||||
this.setData({
|
||||
appealEvidenceFiles: [...this.data.appealEvidenceFiles, ...files].slice(0, 3)
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
removeEvidence(e) {
|
||||
const idx = Number(e.currentTarget.dataset.index || 0)
|
||||
const files = this.data.appealEvidenceFiles.filter((_, i) => i !== idx)
|
||||
const urls = this.data.appealEvidenceUrls.filter((_, i) => i !== idx)
|
||||
this.setData({ appealEvidenceFiles: files, appealEvidenceUrls: urls })
|
||||
},
|
||||
|
||||
async uploadAllEvidence() {
|
||||
const files = this.data.appealEvidenceFiles
|
||||
if (!files.length) return []
|
||||
|
||||
const urls = []
|
||||
for (const filePath of files) {
|
||||
try {
|
||||
const result = await uploadFile(filePath)
|
||||
urls.push(result.url)
|
||||
} catch (err) {
|
||||
console.error('上传失败', filePath, err)
|
||||
}
|
||||
}
|
||||
return urls
|
||||
},
|
||||
|
||||
cancelAppeal() {
|
||||
this.setData({ appealPostId: null, appealReason: '' })
|
||||
this.setData({
|
||||
appealPostId: null,
|
||||
appealReasonType: '',
|
||||
appealReasonTypeIndex: 0,
|
||||
appealReason: '',
|
||||
appealEvidenceUrls: [],
|
||||
appealEvidenceFiles: []
|
||||
})
|
||||
},
|
||||
|
||||
async submitAppeal() {
|
||||
const postId = this.data.appealPostId
|
||||
if (!postId) return
|
||||
|
||||
const reasonType = (this.data.appealReasonType || '').trim()
|
||||
const reason = (this.data.appealReason || '').trim()
|
||||
if (reason.length < 2) {
|
||||
|
||||
// 如果没有选择快捷理由类型,必须手动填写理由
|
||||
if (!reasonType && reason.length < 2) {
|
||||
wx.showToast({ title: '申诉理由至少 2 个字符', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// 上传证据图片
|
||||
const evidenceUrls = await this.uploadAllEvidence()
|
||||
|
||||
await request({
|
||||
url: `/content/posts/${postId}/appeal`,
|
||||
method: 'POST',
|
||||
data: { reason }
|
||||
data: {
|
||||
reason_type: reasonType,
|
||||
reason,
|
||||
evidence_urls: evidenceUrls
|
||||
}
|
||||
})
|
||||
wx.showToast({ title: '申诉提交成功', icon: 'success' })
|
||||
this.setData({ appealPostId: null, appealReason: '' })
|
||||
this.setData({
|
||||
appealPostId: null,
|
||||
appealReasonType: '',
|
||||
appealReasonTypeIndex: 0,
|
||||
appealReason: '',
|
||||
appealEvidenceUrls: [],
|
||||
appealEvidenceFiles: []
|
||||
})
|
||||
this.fetchList()
|
||||
},
|
||||
|
||||
|
||||
@@ -61,7 +61,25 @@
|
||||
</view>
|
||||
|
||||
<view wx:if="{{appealPostId === item.id}}">
|
||||
<textarea class="textarea" placeholder="请输入申诉理由(至少 2 个字符)" value="{{appealReason}}" bindinput="onAppealInput" />
|
||||
<view class="field">
|
||||
<text class="field-label">申诉理由类型</text>
|
||||
<picker mode="selector" range="{{appealReasonTypeOptions}}" range-key="label" value="{{appealReasonTypeIndex}}" bindchange="onReasonTypeChange">
|
||||
<view class="picker-value">{{appealReasonTypeOptions[appealReasonTypeIndex].label}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<textarea class="textarea" placeholder="可补充申诉理由(选择快捷理由后可省略)" value="{{appealReason}}" bindinput="onAppealInput" />
|
||||
<view class="field">
|
||||
<text class="field-label">证据截图(最多3张)</text>
|
||||
<view class="evidence-grid">
|
||||
<view class="evidence-item" wx:for="{{appealEvidenceFiles}}" wx:key="index">
|
||||
<image class="evidence-thumb" src="{{item}}" mode="aspectFill" />
|
||||
<view class="evidence-remove" data-index="{{index}}" bindtap="removeEvidence">×</view>
|
||||
</view>
|
||||
<view class="evidence-add" wx:if="{{appealEvidenceFiles.length < 3}}" bindtap="chooseEvidence">
|
||||
<text class="evidence-add-icon">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btn-row">
|
||||
<button class="btn btn-primary" bindtap="submitAppeal">提交申诉</button>
|
||||
<button class="btn btn-ghost" bindtap="cancelAppeal">取消</button>
|
||||
|
||||
Reference in New Issue
Block a user