feat: 新增教师看板
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
Some checks failed
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Lint (ubuntu-latest) (push) Has been cancelled
CI / Lint (windows-latest) (push) Has been cancelled
CI / Check (ubuntu-latest) (push) Has been cancelled
CI / Check (windows-latest) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Website on push / Deploy Push Playground Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Docs Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Antd Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Element Ftp (push) Has been cancelled
Deploy Website on push / Deploy Push Naive Ftp (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
CI / CI OK (push) Has been cancelled
Deploy Website on push / Rerun on failure (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
This commit is contained in:
parent
db6f433179
commit
1bfec1424a
@ -64,3 +64,35 @@ export async function getClassHomeworkList(
|
|||||||
const res = await get<any>('/school/report/classHomework/list', params)
|
const res = await get<any>('/school/report/classHomework/list', params)
|
||||||
return res ?? {}
|
return res ?? {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** AI 批改看板 - 概览数据 */
|
||||||
|
export interface AiCorrectionOverview {
|
||||||
|
classId: number | string
|
||||||
|
className: string
|
||||||
|
aiPaperCount: number
|
||||||
|
aiTitleCount: number
|
||||||
|
teacherSaveTimeMinutes: number
|
||||||
|
excellentCount: number
|
||||||
|
excellentRate: number
|
||||||
|
goodCount: number
|
||||||
|
goodRate: number
|
||||||
|
passCount: number
|
||||||
|
passRate: number
|
||||||
|
unsubmittedCount: number
|
||||||
|
unsubmittedRate: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AiCorrectionParams {
|
||||||
|
classId: string
|
||||||
|
schoolId?: string
|
||||||
|
subjectIds?: number[]
|
||||||
|
startTime?: string
|
||||||
|
endTime?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAiCorrectionOverview(
|
||||||
|
params: AiCorrectionParams,
|
||||||
|
): Promise<AiCorrectionOverview> {
|
||||||
|
const res = await get<any>('/school/report/aiCorrectionEfficiency/overview', params)
|
||||||
|
return res ?? {}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {
|
import type {
|
||||||
|
AiCorrectionOverview,
|
||||||
ClassHomeworkResult,
|
ClassHomeworkResult,
|
||||||
ClassItem,
|
ClassItem,
|
||||||
StudentHomework,
|
StudentHomework,
|
||||||
@ -7,17 +8,25 @@ import type {
|
|||||||
} from '#/api/service/org/teacher-kanban'
|
} from '#/api/service/org/teacher-kanban'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
Aim,
|
||||||
CircleCheck,
|
CircleCheck,
|
||||||
|
CircleCheckFilled,
|
||||||
|
Clock,
|
||||||
|
Coin,
|
||||||
|
Collection,
|
||||||
DataAnalysis,
|
DataAnalysis,
|
||||||
|
DataBoard,
|
||||||
Document,
|
Document,
|
||||||
Refresh,
|
Refresh,
|
||||||
Search,
|
Search,
|
||||||
|
TrendCharts,
|
||||||
User,
|
User,
|
||||||
Warning,
|
Warning,
|
||||||
} from '@element-plus/icons-vue'
|
} from '@element-plus/icons-vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
getAiCorrectionOverview,
|
||||||
getClassHomeworkList,
|
getClassHomeworkList,
|
||||||
getClassListByTeacher,
|
getClassListByTeacher,
|
||||||
getSubjectsByClassId,
|
getSubjectsByClassId,
|
||||||
@ -30,6 +39,9 @@ const selectedClassId = ref('')
|
|||||||
const selectedSubjectIds = ref<string[]>([])
|
const selectedSubjectIds = ref<string[]>([])
|
||||||
const dateRange = ref<[string, string] | undefined>(undefined)
|
const dateRange = ref<[string, string] | undefined>(undefined)
|
||||||
const result = ref<ClassHomeworkResult | null>(null)
|
const result = ref<ClassHomeworkResult | null>(null)
|
||||||
|
const aiOverview = ref<AiCorrectionOverview | null>(null)
|
||||||
|
const aiLoading = ref(false)
|
||||||
|
const aiErrorMsg = ref('')
|
||||||
|
|
||||||
const activeShortcut = ref('')
|
const activeShortcut = ref('')
|
||||||
const shortcuts = [
|
const shortcuts = [
|
||||||
@ -108,6 +120,31 @@ const summaryStats = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const saveTimeText = computed(() => {
|
||||||
|
const mins = aiOverview.value?.teacherSaveTimeMinutes || 0
|
||||||
|
if (mins <= 0) return 'AI 小助手已准备好为您分担批阅工作。'
|
||||||
|
if (mins >= 1440) return `AI 小助手已为您节省 ${(mins / 1440).toFixed(1).replace(/\.0$/, '')} 天。`
|
||||||
|
if (mins >= 60) return `AI 小助手已为您节省 ${(mins / 60).toFixed(1).replace(/\.0$/, '')} 小时。`
|
||||||
|
return `AI 小助手已为您节省 ${mins} 分钟。`
|
||||||
|
})
|
||||||
|
|
||||||
|
const gradeItems = computed(() => {
|
||||||
|
const data = aiOverview.value
|
||||||
|
return [
|
||||||
|
{ label: '优秀', count: data?.excellentCount || 0, rate: data?.excellentRate || 0, type: 'excellent' },
|
||||||
|
{ label: '良好', count: data?.goodCount || 0, rate: data?.goodRate || 0, type: 'good' },
|
||||||
|
{ label: '合格', count: data?.passCount || 0, rate: data?.passRate || 0, type: 'pass' },
|
||||||
|
{ label: '未提交', count: data?.unsubmittedCount || 0, rate: data?.unsubmittedRate || 0, type: 'unsubmit' },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const normalizeRate = (rate?: number | null) => {
|
||||||
|
if (!rate) return 0
|
||||||
|
return rate > 1 ? rate : rate * 100
|
||||||
|
}
|
||||||
|
const formatRate = (rate?: number | null) => `${normalizeRate(rate).toFixed(1).replace(/\.0$/, '')}%`
|
||||||
|
const rateWidth = (rate?: number | null) => `${Math.min(100, Math.max(0, normalizeRate(rate)))}%`
|
||||||
|
|
||||||
function getSubjectStat(student: StudentHomework, subjectId: any) {
|
function getSubjectStat(student: StudentHomework, subjectId: any) {
|
||||||
return student.subjectStats?.find(
|
return student.subjectStats?.find(
|
||||||
(s) => String(s.subjectId) === String(subjectId),
|
(s) => String(s.subjectId) === String(subjectId),
|
||||||
@ -158,6 +195,8 @@ async function loadSubjects() {
|
|||||||
async function search() {
|
async function search() {
|
||||||
if (!selectedClassId.value) return
|
if (!selectedClassId.value) return
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
aiLoading.value = true
|
||||||
|
aiErrorMsg.value = ''
|
||||||
try {
|
try {
|
||||||
const params: any = { classId: selectedClassId.value }
|
const params: any = { classId: selectedClassId.value }
|
||||||
if (dateRange.value?.[0]) params.startTime = dateRange.value[0]
|
if (dateRange.value?.[0]) params.startTime = dateRange.value[0]
|
||||||
@ -165,9 +204,19 @@ async function search() {
|
|||||||
if (selectedSubjectIds.value.length > 0) {
|
if (selectedSubjectIds.value.length > 0) {
|
||||||
params.subjectIds = selectedSubjectIds.value
|
params.subjectIds = selectedSubjectIds.value
|
||||||
}
|
}
|
||||||
result.value = await getClassHomeworkList(params)
|
const [homeworkRes, overviewRes] = await Promise.all([
|
||||||
|
getClassHomeworkList(params),
|
||||||
|
getAiCorrectionOverview(params).catch((err) => {
|
||||||
|
console.error('获取AI看板数据失败', err)
|
||||||
|
aiErrorMsg.value = 'AI看板数据加载失败'
|
||||||
|
return null
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
result.value = homeworkRes
|
||||||
|
aiOverview.value = overviewRes
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
aiLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +230,8 @@ function handleReset() {
|
|||||||
function onClassChange() {
|
function onClassChange() {
|
||||||
selectedSubjectIds.value = []
|
selectedSubjectIds.value = []
|
||||||
result.value = null
|
result.value = null
|
||||||
|
aiOverview.value = null
|
||||||
|
aiErrorMsg.value = ''
|
||||||
loadSubjects()
|
loadSubjects()
|
||||||
search()
|
search()
|
||||||
}
|
}
|
||||||
@ -280,6 +331,91 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- AI 批改看板 -->
|
||||||
|
<div class="ai-dashboard">
|
||||||
|
<div class="ai-dashboard__header">
|
||||||
|
<div class="ai-dashboard__title-row">
|
||||||
|
<el-icon :size="22" color="#4f46e5"><DataBoard /></el-icon>
|
||||||
|
<span class="ai-dashboard__title">AI 批改看板</span>
|
||||||
|
<span class="ai-dashboard__subtitle">按班级、学科和日期查看效率数据</span>
|
||||||
|
</div>
|
||||||
|
<el-tag v-if="aiOverview?.className" type="info" effect="plain" size="small">
|
||||||
|
{{ aiOverview.className }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载态 -->
|
||||||
|
<div v-if="aiLoading" class="ai-dashboard__loading">
|
||||||
|
<el-skeleton :rows="3" animated />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-else-if="!selectedClassId" class="ai-dashboard__empty">
|
||||||
|
<el-icon :size="40" color="#c0c4cc"><DataBoard /></el-icon>
|
||||||
|
<p>请选择班级查看看板</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误态 -->
|
||||||
|
<div v-else-if="aiErrorMsg" class="ai-dashboard__empty">
|
||||||
|
<el-icon :size="40" color="#f56c6c"><Warning /></el-icon>
|
||||||
|
<p style="color: #f56c6c">{{ aiErrorMsg }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 数据内容 -->
|
||||||
|
<template v-else-if="aiOverview">
|
||||||
|
<div class="ai-dashboard__metrics">
|
||||||
|
<div class="ai-metric-card">
|
||||||
|
<div class="ai-metric-card__icon ai-metric-card__icon--blue">
|
||||||
|
<el-icon :size="22"><Collection /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="ai-metric-card__content">
|
||||||
|
<span class="ai-metric-card__value">{{ aiOverview.aiPaperCount || 0 }}</span>
|
||||||
|
<span class="ai-metric-card__label">批改试卷</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ai-metric-card">
|
||||||
|
<div class="ai-metric-card__icon ai-metric-card__icon--green">
|
||||||
|
<el-icon :size="22"><Aim /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="ai-metric-card__content">
|
||||||
|
<span class="ai-metric-card__value">{{ aiOverview.aiTitleCount || 0 }}</span>
|
||||||
|
<span class="ai-metric-card__label">批改题目</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ai-save-card">
|
||||||
|
<el-icon :size="18" color="#16a34a" style="flex-shrink:0"><Clock /></el-icon>
|
||||||
|
<div class="ai-save-card__content">
|
||||||
|
<span class="ai-save-card__title">节省批阅时间</span>
|
||||||
|
<span class="ai-save-card__text">{{ saveTimeText }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ai-grade-card">
|
||||||
|
<div class="ai-grade-card__header">
|
||||||
|
<el-icon :size="18" color="#4f46e5"><TrendCharts /></el-icon>
|
||||||
|
<span class="ai-grade-card__title">成绩分布</span>
|
||||||
|
</div>
|
||||||
|
<div class="ai-grade-list">
|
||||||
|
<div v-for="item in gradeItems" :key="item.label" class="ai-grade-item">
|
||||||
|
<div class="ai-grade-item__top">
|
||||||
|
<span class="ai-grade-item__name">{{ item.label }}</span>
|
||||||
|
<span class="ai-grade-item__count">{{ item.count }}人 · {{ formatRate(item.rate) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="ai-grade-bar">
|
||||||
|
<div
|
||||||
|
class="ai-grade-fill"
|
||||||
|
:class="`ai-grade-fill--${item.type}`"
|
||||||
|
:style="{ width: rateWidth(item.rate) }"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
<!-- 统计卡片 -->
|
||||||
<div v-if="result?.className" class="stats-row">
|
<div v-if="result?.className" class="stats-row">
|
||||||
<div class="stat-card stat-card--primary">
|
<div class="stat-card stat-card--primary">
|
||||||
@ -907,4 +1043,219 @@ onMounted(async () => {
|
|||||||
color: #ff584e;
|
color: #ff584e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── AI 批改看板 ──
|
||||||
|
.ai-dashboard {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 20px 24px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 3px rgb(0 0 0 / 5%), 0 1px 2px rgb(0 0 0 / 3%);
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #312e81;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__loading {
|
||||||
|
min-height: 180px;
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 180px;
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__metrics {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 14px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-metric-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 18px 20px;
|
||||||
|
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #eef2ff;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-color: #c7d2fe;
|
||||||
|
box-shadow: 0 2px 8px rgb(79 70 229 / 8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 10px;
|
||||||
|
|
||||||
|
&--blue {
|
||||||
|
color: #4f46e5;
|
||||||
|
background: linear-gradient(135deg, #eef2ff, #dce8fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--green {
|
||||||
|
color: #16a34a;
|
||||||
|
background: linear-gradient(135deg, #edfcf7, #d4f5ea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.2;
|
||||||
|
color: #1e293b;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-save-card {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px 20px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #bbf7d0;
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-grade-card {
|
||||||
|
padding: 18px 20px;
|
||||||
|
background: linear-gradient(135deg, #f8fafc, #f1f5f9);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #eef2ff;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #312e81;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-grade-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-grade-item {
|
||||||
|
&__top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-grade-bar {
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #eef2ff;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ai-grade-fill {
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 999px;
|
||||||
|
transition: width 360ms ease;
|
||||||
|
|
||||||
|
&--excellent {
|
||||||
|
background: linear-gradient(90deg, #22c55e, #4ade80);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--good {
|
||||||
|
background: linear-gradient(90deg, #3b82f6, #60a5fa);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--pass {
|
||||||
|
background: linear-gradient(90deg, #f59e0b, #fbbf24);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--unsubmit {
|
||||||
|
background: linear-gradient(90deg, #ef4444, #f87171);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user