chore: establish V1.1 recovery baseline
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 申诉提交页(重构:用 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();
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "提交申诉",
|
||||
"backgroundColor": "#FFF8F7",
|
||||
"backgroundTextStyle": "dark",
|
||||
"usingComponents": {
|
||||
"ui-nav-bar": "/components/nav-bar/index",
|
||||
"ui-section-header": "/components/section-header/index",
|
||||
"ui-textarea": "/components/textarea/index"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<!-- 申诉提交页(重构:appealService + ui-nav-bar + ui-textarea) -->
|
||||
<view class="page">
|
||||
<ui-nav-bar
|
||||
title="{{typeLabel}}"
|
||||
show-back="{{true}}"
|
||||
status-bar-height="{{statusBarHeight}}"
|
||||
nav-bar-height="{{navBarHeight}}"
|
||||
bind:back="onBack"
|
||||
/>
|
||||
|
||||
<!-- 提交成功态 -->
|
||||
<view wx:if="{{submitted}}" class="body" style="padding-top: {{statusBarHeight + navBarHeight}}px;">
|
||||
<view class="success-wrap">
|
||||
<view class="success-icon">✓</view>
|
||||
<text class="success-title">申诉已提交</text>
|
||||
<text class="success-hint">工作人员将在 1-3 个工作日内审核您的申诉结果,请耐心等待。</text>
|
||||
<button class="btn-primary" bindtap="goBack">返回</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 表单态 -->
|
||||
<view wx:else class="body" style="padding-top: {{statusBarHeight + navBarHeight}}px;">
|
||||
<view class="intro-card">
|
||||
<text class="intro-title">{{typeLabel}}</text>
|
||||
<text class="intro-subtitle">请如实描述您的情况,工作人员将认真审核</text>
|
||||
</view>
|
||||
|
||||
<!-- 申诉原因 -->
|
||||
<view class="card">
|
||||
<ui-section-header title="申诉原因" subtitle="必填,至少10字" />
|
||||
<ui-textarea
|
||||
value="{{reason}}"
|
||||
placeholder="请详细说明您认为处置不当的原因,以及您希望恢复账号的理由..."
|
||||
maxlength="500"
|
||||
error="{{reasonError}}"
|
||||
bind:input="onReasonInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 补充证据 -->
|
||||
<view class="card">
|
||||
<ui-section-header title="补充证据" subtitle="选填,最多5张" />
|
||||
<view class="evidence-list">
|
||||
<view
|
||||
class="evidence-item"
|
||||
wx:for="{{evidenceFiles}}"
|
||||
wx:key="index"
|
||||
data-url="{{item}}"
|
||||
bindtap="previewImage"
|
||||
>
|
||||
<image src="{{item}}" mode="aspectFill" />
|
||||
<view class="remove-btn" catchtap="removeImage" data-index="{{index}}">✕</view>
|
||||
</view>
|
||||
<view class="evidence-add" bindtap="chooseEvidence" wx:if="{{evidenceFiles.length < 5}}">
|
||||
<text class="add-icon">+</text>
|
||||
<text class="add-text">添加证据</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<view class="card">
|
||||
<ui-section-header title="联系方式" subtitle="选填" />
|
||||
<input
|
||||
class="contact-input"
|
||||
placeholder="请输入手机号或邮箱,便于我们联系您"
|
||||
value="{{contactMethod}}"
|
||||
bindinput="onContactInput"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<button class="btn-submit" bindtap="submitAppeal" loading="{{submitting}}" disabled="{{!reason}}">
|
||||
提交申诉
|
||||
</button>
|
||||
<text class="hint">提交后工作人员将在 1-3 个工作日内审核,请勿重复提交</text>
|
||||
|
||||
<!-- 公益免费标识 -->
|
||||
<view class="free-badge">
|
||||
<text>本平台为公益服务,申诉受理永久免费</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,209 @@
|
||||
/* 申诉提交页 — Design Tokens */
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #FFF8F7;
|
||||
}
|
||||
|
||||
.body {
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 介绍卡片 */
|
||||
.intro-card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
border-left: 8rpx solid #E96B6B;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
display: block;
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.intro-subtitle {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 24rpx 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 证据 */
|
||||
.evidence-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.evidence-item {
|
||||
position: relative;
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.evidence-item image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
position: absolute;
|
||||
top: 4rpx;
|
||||
right: 4rpx;
|
||||
width: 36rpx;
|
||||
height: 36rpx;
|
||||
line-height: 32rpx;
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.evidence-add {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
background: #F7F8FA;
|
||||
border: 2rpx dashed #D0D0D0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.add-icon {
|
||||
font-size: 48rpx;
|
||||
color: #B0B0B0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.add-text {
|
||||
font-size: 22rpx;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
/* 联系方式输入 */
|
||||
.contact-input {
|
||||
margin-top: 16rpx;
|
||||
background: #F7F8FA;
|
||||
border-radius: 12rpx;
|
||||
padding: 24rpx;
|
||||
font-size: 28rpx;
|
||||
min-height: 40rpx;
|
||||
}
|
||||
|
||||
/* 提交按钮 */
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background: #E96B6B;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.btn-submit::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-submit[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 80%;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 44rpx;
|
||||
background: #E96B6B;
|
||||
color: #fff;
|
||||
font-size: 32rpx;
|
||||
border: none;
|
||||
margin-top: 48rpx;
|
||||
}
|
||||
|
||||
.btn-primary::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.hint {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 22rpx;
|
||||
color: #B0B0B0;
|
||||
margin-top: 20rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
/* 成功态 */
|
||||
.success-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 120rpx 48rpx;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
line-height: 120rpx;
|
||||
text-align: center;
|
||||
border-radius: 50%;
|
||||
background: #E8F5E9;
|
||||
color: #1A6B3E;
|
||||
font-size: 64rpx;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #1A1A1A;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.success-hint {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
text-align: center;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 公益免费标识 */
|
||||
.free-badge {
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.free-badge text {
|
||||
display: inline-block;
|
||||
font-size: 22rpx;
|
||||
color: #1A6B3E;
|
||||
background: #E8F5E9;
|
||||
padding: 8rpx 24rpx;
|
||||
border-radius: 24rpx;
|
||||
}
|
||||
Reference in New Issue
Block a user