Files
c/miniprogram/pages/admin-review/index.wxml
刘正航 f7d0601c4e 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>
2026-04-21 23:26:25 +08:00

133 lines
6.7 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<view class="container">
<view class="hero fade-up">
<view class="hero-badge">REVIEW CENTER</view>
<view class="hero-title">复核与申诉后台</view>
<view class="hero-sub">支持阈值调节、拦截复核、申诉处理与分页筛选,满足商用审核流程。</view>
</view>
<view class="card fade-up fade-up-delay-1">
<view class="card-title">检测阈值调节</view>
<view class="card-desc">阈值越低拦截越严格。推荐范围0.65 - 0.85。</view>
<view class="row">
<text class="label">垃圾阈值0-1</text>
<input class="input threshold-input" type="digit" value="{{thresholdInput}}" data-field="thresholdInput" bindinput="onInput" />
</view>
<button class="btn btn-primary" bindtap="saveThreshold">更新阈值</button>
</view>
<view class="card fade-up fade-up-delay-1">
<view class="card-title">拦截记录筛选</view>
<view class="field">
<text class="field-label">关键词</text>
<input class="input" placeholder="搜索拦截文本关键词" value="{{interceptKeyword}}" data-field="interceptKeyword" bindinput="onInput" />
</view>
<view class="row">
<text class="label">发布状态</text>
<picker mode="selector" range="{{interceptStatusOptions}}" range-key="label" value="{{interceptStatusIndex}}" bindchange="onInterceptStatusChange">
<view class="picker-value">{{interceptStatusOptions[interceptStatusIndex].label}}</view>
</picker>
</view>
<view class="row">
<text class="label">复核状态</text>
<picker mode="selector" range="{{interceptReviewStatusOptions}}" range-key="label" value="{{interceptReviewStatusIndex}}" bindchange="onInterceptReviewStatusChange">
<view class="picker-value">{{interceptReviewStatusOptions[interceptReviewStatusIndex].label}}</view>
</picker>
</view>
<view class="btn-row">
<button class="btn btn-primary" bindtap="applyInterceptFilters">查询</button>
<button class="btn btn-ghost" bindtap="clearInterceptFilters">重置</button>
</view>
</view>
<view class="card fade-up fade-up-delay-2">
<view class="card-title">信息拦截人工复核</view>
<view class="muted">共 {{interceptPager.total}} 条,第 {{interceptPager.page}} / {{interceptPager.totalPages}} 页</view>
<view wx:if="{{intercepts.length}}">
<view class="list-item" wx:for="{{intercepts}}" wx:key="id">
<view class="item-title">{{item.text}}</view>
<view class="item-sub">用户:{{item.nickname || item.username}} · 垃圾概率:{{item.spam_probability_text}}</view>
<view class="item-sub">复核状态:{{item.review_status_text}} · 申诉状态:{{item.appeal_status_text}}</view>
<view class="item-sub">发布时间:{{item.created_text}}</view>
<textarea class="textarea note-textarea" placeholder="可填写复核备注(将写入处理记录)" value="{{reviewNoteMap[item.id] || ''}}" data-id="{{item.id}}" bindinput="onReviewNoteInput" />
<view class="btn-row">
<button class="btn btn-accent" data-id="{{item.id}}" data-decision="spam" bindtap="reviewIntercept">确认垃圾</button>
<button class="btn btn-ghost" data-id="{{item.id}}" data-decision="ham" bindtap="reviewIntercept">误判放行</button>
</view>
</view>
<view class="pager-row">
<button class="btn btn-ghost pager-btn" disabled="{{!interceptPager.hasPrev}}" bindtap="prevInterceptPage">上一页</button>
<button class="btn btn-ghost pager-btn" disabled="{{!interceptPager.hasNext}}" bindtap="nextInterceptPage">下一页</button>
</view>
</view>
<view wx:else class="empty">没有匹配的拦截记录。</view>
</view>
<view class="card fade-up fade-up-delay-2">
<view class="card-title">申诉记录筛选</view>
<view class="field">
<text class="field-label">关键词</text>
<input class="input" placeholder="搜索申诉文本或申诉理由" value="{{appealKeyword}}" data-field="appealKeyword" bindinput="onInput" />
</view>
<view class="row">
<text class="label">申诉状态</text>
<picker mode="selector" range="{{appealStatusOptions}}" range-key="label" value="{{appealStatusIndex}}" bindchange="onAppealStatusChange">
<view class="picker-value">{{appealStatusOptions[appealStatusIndex].label}}</view>
</picker>
</view>
<view class="btn-row">
<button class="btn btn-primary" bindtap="applyAppealFilters">查询</button>
<button class="btn btn-ghost" bindtap="clearAppealFilters">重置</button>
</view>
</view>
<view class="card fade-up fade-up-delay-3">
<view class="card-title">拦截信息申诉处理</view>
<view class="muted">共 {{appealPager.total}} 条,第 {{appealPager.page}} / {{appealPager.totalPages}} 页</view>
<view wx:if="{{appeals.length}}">
<view class="list-item" wx:for="{{appeals}}" wx:key="id">
<view class="item-title">{{item.text}}</view>
<view class="item-sub">申诉人:{{item.nickname || item.username}} · 当前状态:{{item.appeal_status_text}}</view>
<view class="item-sub">申诉理由类型:{{item.appeal_reason_type || '未选择'}}</view>
<view class="item-sub">申诉理由:{{item.appeal_reason || '未填写'}}</view>
<view class="item-sub">时间:{{item.created_text}}</view>
<view class="field" wx:if="{{item.appeal_evidence_urls && item.appeal_evidence_urls.length}}">
<text class="field-label">证据截图</text>
<view class="evidence-grid">
<view class="evidence-item" wx:for="{{item.appeal_evidence_urls}}" wx:for-item="evidenceUrl" wx:key="*this">
<image class="evidence-thumb evidence-clickable" src="{{evidenceUrl}}" mode="aspectFill" data-url="{{evidenceUrl}}" bindtap="previewEvidence" />
</view>
</view>
</view>
<textarea class="textarea note-textarea" placeholder="可填写申诉处理备注" value="{{appealNoteMap[item.id] || ''}}" data-id="{{item.id}}" bindinput="onAppealNoteInput" />
<view class="btn-row">
<button class="btn btn-primary" data-id="{{item.id}}" data-action="approve" bindtap="processAppeal">通过申诉</button>
<button class="btn btn-ghost" data-id="{{item.id}}" data-action="reject" bindtap="processAppeal">驳回申诉</button>
</view>
</view>
<view class="pager-row">
<button class="btn btn-ghost pager-btn" disabled="{{!appealPager.hasPrev}}" bindtap="prevAppealPage">上一页</button>
<button class="btn btn-ghost pager-btn" disabled="{{!appealPager.hasNext}}" bindtap="nextAppealPage">下一页</button>
</view>
</view>
<view wx:else class="empty">没有匹配的申诉记录。</view>
</view>
</view>