feat(users): enforce real bans from report processing and fix appeal lifting

- apply_report_action now writes aksrt_user_restriction via Restriction::restrict
  and lifts via new lift_user_restriction(), replacing the display-only
  aksrt_user_ban_state meta dual-track
- ban type restrictions destroy the target user's sessions on apply
- appeal approval lifts active ban/suspend/mute restrictions instead of
  updating a nonexistent status column
- UsersBan.vue posts to /users/{id}/restrict with valid types (warn/mute/
  suspend/ban), scope and expires_at
- add comment report entry to the comment actions dropdown
- add duplicate pending report guard on submit
- remove dead ReportCenter ajax handlers, option migration, REST alias
  routes and write-only restriction meta
- fix report admin mail link to the real aksrt_user_ban page
- add report-ban contract tests
This commit is contained in:
2026-09-19 21:38:56 +08:00
parent 4dafe96c4b
commit 835e0f983d
12 changed files with 291 additions and 214 deletions
+26 -8
View File
@@ -52,6 +52,9 @@
<n-form-item label="限制类型">
<n-select v-model:value="addForm.type" :options="restrictionTypes" style="width:200px;" />
</n-form-item>
<n-form-item label="限制范围">
<n-select v-model:value="addForm.scope" :options="scopeOptions" style="width:200px;" />
</n-form-item>
<n-form-item label="原因">
<n-input v-model:value="addForm.reason" type="textarea" :rows="2" placeholder="限制原因(必填)" />
</n-form-item>
@@ -84,15 +87,22 @@ const stats = reactive({ total: 0, active: 0, banned: 0 })
const addForm = reactive({
user_id: null,
type: 'muted',
type: 'mute',
scope: 'global',
reason: '',
duration: '7d',
})
const restrictionTypes = [
{ label: '禁言', value: 'muted' },
{ label: '限制', value: 'restricted' },
{ label: '封禁', value: 'banned' },
{ label: '警告', value: 'warn' },
{ label: '禁言', value: 'mute' },
{ label: '停权', value: 'suspend' },
{ label: '封禁', value: 'ban' },
]
const scopeOptions = [
{ label: '全局', value: 'global' },
{ label: '仅评论', value: 'comment' },
]
const durationOptions = [
@@ -126,15 +136,22 @@ const columns = [
]
function getTypeTag(type) {
const map = { muted: 'warning', restricted: 'error', banned: 'error' }
const map = { warn: 'info', mute: 'warning', suspend: 'warning', ban: 'error' }
return map[type] || 'default'
}
function getTypeLabel(type) {
const map = { muted: '禁言', restricted: '限制', banned: '封禁' }
const map = { warn: '警告', mute: '禁言', suspend: '停权', ban: '封禁' }
return map[type] || type
}
function durationToExpiresAt(duration) {
if (!duration || duration === 'permanent') return ''
const days = parseInt(duration, 10)
if (!Number.isFinite(days) || days <= 0) return ''
return new Date(Date.now() + days * 86400000).toISOString()
}
async function loadStats() {
try {
const res = await api.request('/users/restrictions/stats')
@@ -174,12 +191,13 @@ async function addRestriction() {
}
adding.value = true
try {
await api.request(`/users/${addForm.user_id}/restrictions`, {
await api.request(`/users/${addForm.user_id}/restrict`, {
method: 'POST',
data: {
type: addForm.type,
scope: addForm.scope,
reason: addForm.reason,
duration: addForm.duration,
expires_at: durationToExpiresAt(addForm.duration),
},
})
message.success('限制已添加')