feat(widgets): add phase 4 validation and accessibility
This commit is contained in:
1 parent
8cad3c2d3d
commit
e1723a8f08
117 files changed
+6
-17338
No files matched your search
@@ -1,210 +0,0 @@
|
||||
<template>
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="禁封管理"
|
||||
desc="查看和管理用户封禁、限制状态,支持按用户 ID 查询和解除限制。"
|
||||
/>
|
||||
|
||||
<n-grid cols="1 s:2 m:3" :x-gap="16" :y-gap="16" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-statistic label="总限制记录" :value="stats.total" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-statistic label="当前生效" :value="stats.active">
|
||||
<template #suffix>
|
||||
<n-tag v-if="stats.active > 0" type="error" size="small">{{ stats.active }}</n-tag>
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-statistic label="封禁账号" :value="stats.banned">
|
||||
<template #suffix>
|
||||
<n-tag v-if="stats.banned > 0" type="error" size="small">严重</n-tag>
|
||||
</template>
|
||||
</n-statistic>
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
|
||||
<!-- Search by user ID -->
|
||||
<n-card size="small" title="查询用户限制" style="margin-top:16px;">
|
||||
<n-space>
|
||||
<n-input-number v-model:value="searchUserId" placeholder="用户 ID" :min="1" style="width:160px;" />
|
||||
<n-button type="primary" @click="searchRestrictions" :loading="searching">查询</n-button>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<!-- User restrictions list -->
|
||||
<n-card v-if="userRestrictions.length" size="small" :title="`用户 #${searchUserId} 的限制记录`" style="margin-top:16px;">
|
||||
<n-data-table
|
||||
:columns="columns"
|
||||
:data="userRestrictions"
|
||||
:loading="searching"
|
||||
:bordered="false"
|
||||
/>
|
||||
</n-card>
|
||||
|
||||
<!-- Add restriction form -->
|
||||
<n-card size="small" title="添加限制" style="margin-top:16px;">
|
||||
<n-form :model="addForm" label-placement="left" label-width="100" class="aksrt-form">
|
||||
<n-form-item label="用户 ID">
|
||||
<n-input-number v-model:value="addForm.user_id" :min="1" style="width:200px;" />
|
||||
</n-form-item>
|
||||
<n-form-item label="限制类型">
|
||||
<n-select v-model:value="addForm.type" :options="restrictionTypes" 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>
|
||||
<n-form-item label="持续时间">
|
||||
<n-select v-model:value="addForm.duration" :options="durationOptions" style="width:200px;" />
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-button type="error" @click="addRestriction" :loading="adding" :disabled="!addForm.user_id || !addForm.reason">
|
||||
添加限制
|
||||
</n-button>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { h, reactive, ref, onMounted } from 'vue'
|
||||
import { NButton, NTag, NSpace, useMessage } from 'naive-ui'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import api from '@/api'
|
||||
|
||||
const message = useMessage()
|
||||
|
||||
const searching = ref(false)
|
||||
const adding = ref(false)
|
||||
const searchUserId = ref(null)
|
||||
const userRestrictions = ref([])
|
||||
const stats = reactive({ total: 0, active: 0, banned: 0 })
|
||||
|
||||
const addForm = reactive({
|
||||
user_id: null,
|
||||
type: 'muted',
|
||||
reason: '',
|
||||
duration: '7d',
|
||||
})
|
||||
|
||||
const restrictionTypes = [
|
||||
{ label: '禁言', value: 'muted' },
|
||||
{ label: '限制', value: 'restricted' },
|
||||
{ label: '封禁', value: 'banned' },
|
||||
]
|
||||
|
||||
const durationOptions = [
|
||||
{ label: '1 天', value: '1d' },
|
||||
{ label: '3 天', value: '3d' },
|
||||
{ label: '7 天', value: '7d' },
|
||||
{ label: '30 天', value: '30d' },
|
||||
{ label: '永久', value: 'permanent' },
|
||||
]
|
||||
|
||||
const columns = [
|
||||
{ title: 'ID', key: 'id', width: 60 },
|
||||
{
|
||||
title: '类型', key: 'type', width: 80,
|
||||
render: (row) => h(NTag, { type: getTypeTag(row.type), size: 'small' }, () => getTypeLabel(row.type)),
|
||||
},
|
||||
{ title: '原因', key: 'reason', ellipsis: { tooltip: true } },
|
||||
{ title: '生效时间', key: 'created_at', width: 160 },
|
||||
{ title: '到期时间', key: 'expires_at', width: 160 },
|
||||
{
|
||||
title: '状态', key: 'status', width: 80,
|
||||
render: (row) => {
|
||||
const active = !row.expires_at || new Date(row.expires_at) > new Date()
|
||||
return h(NTag, { type: active ? 'error' : 'default', size: 'small' }, () => active ? '生效中' : '已过期')
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作', key: 'actions', width: 100,
|
||||
render: (row) => h(NButton, { size: 'tiny', type: 'error', onClick: () => removeRestriction(row) }, () => '解除'),
|
||||
},
|
||||
]
|
||||
|
||||
function getTypeTag(type) {
|
||||
const map = { muted: 'warning', restricted: 'error', banned: 'error' }
|
||||
return map[type] || 'default'
|
||||
}
|
||||
|
||||
function getTypeLabel(type) {
|
||||
const map = { muted: '禁言', restricted: '限制', banned: '封禁' }
|
||||
return map[type] || type
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const res = await api.request('/users/restrictions/stats')
|
||||
const data = res.data || res
|
||||
stats.total = data.total || 0
|
||||
stats.active = data.active || 0
|
||||
stats.banned = data.banned || 0
|
||||
} catch (e) {
|
||||
// Stats may not be available yet
|
||||
}
|
||||
}
|
||||
|
||||
async function searchRestrictions() {
|
||||
if (!searchUserId.value) {
|
||||
message.warning('请输入用户 ID')
|
||||
return
|
||||
}
|
||||
searching.value = true
|
||||
try {
|
||||
const res = await api.request(`/users/${searchUserId.value}/restrictions`)
|
||||
const data = res.data || res
|
||||
userRestrictions.value = data.restrictions || []
|
||||
if (userRestrictions.value.length === 0) {
|
||||
message.info('该用户无限制记录')
|
||||
}
|
||||
} catch (e) {
|
||||
message.error('查询失败: ' + (e.message || '未知错误'))
|
||||
} finally {
|
||||
searching.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function addRestriction() {
|
||||
if (!addForm.user_id || !addForm.reason) {
|
||||
message.warning('请填写用户 ID 和原因')
|
||||
return
|
||||
}
|
||||
adding.value = true
|
||||
try {
|
||||
await api.request(`/users/${addForm.user_id}/restrictions`, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
type: addForm.type,
|
||||
reason: addForm.reason,
|
||||
duration: addForm.duration,
|
||||
},
|
||||
})
|
||||
message.success('限制已添加')
|
||||
addForm.reason = ''
|
||||
if (searchUserId.value === addForm.user_id) {
|
||||
await searchRestrictions()
|
||||
}
|
||||
await loadStats()
|
||||
} catch (e) {
|
||||
message.error('添加失败: ' + (e.message || '未知错误'))
|
||||
} finally {
|
||||
adding.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function removeRestriction(row) {
|
||||
try {
|
||||
await api.request(`/users/restrictions/${row.id}`, { method: 'DELETE' })
|
||||
message.success('限制已解除')
|
||||
userRestrictions.value = userRestrictions.value.filter((r) => r.id !== row.id)
|
||||
await loadStats()
|
||||
} catch (e) {
|
||||
message.error('解除失败: ' + (e.message || '未知错误'))
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadStats)
|
||||
</script>
|
||||
Reference in new issue
Block a user