新增报到机:实现终端扫码报到核验与受控课堂打卡模拟
This commit is contained in:
1 parent
b1d5a999ca
commit
cecf11790e
16 files changed
+2571
-7
No files matched your search
@@ -0,0 +1,37 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 报到机扫码查询学员报到信息
|
||||
export function lookupTerminal(params) {
|
||||
return request({
|
||||
url: '/train/report-terminal/lookup',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 报到机一键确认报到
|
||||
export function confirmTerminal(data) {
|
||||
return request({
|
||||
url: '/train/report-terminal/confirm',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 模拟设备课堂打卡 (签到/签退)
|
||||
export function mockAttendance(data) {
|
||||
return request({
|
||||
url: '/train/report-terminal/mock-attendance',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 模拟刷卡(通过考勤模块端点)
|
||||
export function mockSwipe(data) {
|
||||
return request({
|
||||
url: '/train/attendance/mock-swipe',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
@@ -107,7 +107,7 @@ export const dynamicRoutes = [
|
||||
component: Layout,
|
||||
hidden: true,
|
||||
redirect: '/train/auth',
|
||||
permissions: ['train:access:query', 'system:auth:list', 'train:accommodation:list'],
|
||||
permissions: ['train:access:query', 'system:auth:list', 'train:accommodation:list', 'train:report:confirm'],
|
||||
children: [
|
||||
{
|
||||
path: 'auth',
|
||||
@@ -122,6 +122,13 @@ export const dynamicRoutes = [
|
||||
name: 'TrainAccommodation',
|
||||
meta: { title: '住宿管理', icon: 'home' },
|
||||
permissions: ['train:accommodation:list']
|
||||
},
|
||||
{
|
||||
path: 'terminal',
|
||||
component: () => import('@/views/train/terminal/index'),
|
||||
name: 'TrainTerminal',
|
||||
meta: { title: '模拟报到机', icon: 'scan' },
|
||||
permissions: ['train:report:confirm']
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
<template>
|
||||
<div class="app-container terminal-container">
|
||||
<el-alert
|
||||
title="【内部测试与设备联调专用】SYFJGYD学员管理系统 — 模拟报到机与受控打卡工作台"
|
||||
type="info"
|
||||
description="本页面模拟现场学员自助报到终端(扫码二维码/查询/核验/一键确认报到)及课堂打卡机。操作仅限已授权测试管理员使用。"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="mb15"
|
||||
/>
|
||||
|
||||
<el-tabs v-model="activeTab" type="border-card">
|
||||
<!-- Tab 1: 模拟报到机 -->
|
||||
<el-tab-pane label="模拟报到机 (Self Check-in Kiosk)" name="reportTerminal">
|
||||
<!-- 扫码/查询区 -->
|
||||
<el-card class="box-card mb15" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold; font-size: 15px;">
|
||||
<i class="el-icon-camera" style="margin-right: 6px; color: #409EFF;" />模拟扫码 / 学号查询
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-form :inline="true" :model="searchForm" size="small" class="demo-form-inline" @submit.native.prevent>
|
||||
<el-form-item label="学员学号 (studentId)">
|
||||
<el-input
|
||||
v-model="searchForm.studentId"
|
||||
placeholder="请输入学号或扫码二维码"
|
||||
clearable
|
||||
style="width: 260px;"
|
||||
prefix-icon="el-icon-user"
|
||||
@keyup.enter.native="handleLookup"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="previewData && previewData.hasMultiplePlans" label="目标开班计划">
|
||||
<el-select
|
||||
v-model="searchForm.planId"
|
||||
placeholder="请明确选择开班计划"
|
||||
style="width: 320px;"
|
||||
@change="handlePlanChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="p in previewData.candidatePlans"
|
||||
:key="p.planId"
|
||||
:label="`${p.planName} (${p.enrollmentStatusLabel})`"
|
||||
:value="p.planId"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" :loading="lookupLoading" @click="handleLookup">
|
||||
查询报到信息
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh-left" @click="handleReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<!-- 查询结果展示区 -->
|
||||
<div v-if="previewData && previewData.studentId">
|
||||
<!-- 状态提示横幅 -->
|
||||
<el-alert
|
||||
v-if="previewData.isReported"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb15"
|
||||
>
|
||||
<span slot="title" style="font-size: 15px; font-weight: bold;">
|
||||
【已报到】学员已完成现场报到(报到时间:{{ parseTime(previewData.reportTime) || '已确认' }})
|
||||
</span>
|
||||
</el-alert>
|
||||
<el-alert
|
||||
v-else
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb15"
|
||||
>
|
||||
<span slot="title" style="font-size: 15px; font-weight: bold;">
|
||||
【待报到】学员尚未报到,请核对学员基础信息、宿舍安排与课程表后点击下方“确认报到”
|
||||
</span>
|
||||
</el-alert>
|
||||
|
||||
<el-row :gutter="15">
|
||||
<!-- 学员基础信息 -->
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never" class="mb15">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold;"><i class="el-icon-user-solid" style="color: #409EFF; margin-right: 5px;" />学员信息与培训班级</span>
|
||||
</div>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="学员姓名">{{ previewData.studentName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员学号">{{ previewData.studentId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ formatGender(previewData.gender) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="联系电话">{{ previewData.phone || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="选派单位" :span="2">{{ previewData.orgName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训班计划" :span="2">
|
||||
<span style="font-weight: bold; color: #303133;">{{ previewData.planName || previewData.planId }}</span>
|
||||
<span style="color: #909399; font-size: 12px; margin-left: 6px;">({{ previewData.planId }})</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="参训状态">
|
||||
<el-tag :type="statusTagType(previewData.enrollmentStatus)" size="small">
|
||||
{{ previewData.enrollmentStatusLabel }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报到状态">
|
||||
<el-tag :type="previewData.isReported ? 'success' : 'info'" size="small">
|
||||
{{ previewData.isReported ? '已报到' : '未报到' }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<!-- 住宿安排 -->
|
||||
<el-col :span="12">
|
||||
<el-card shadow="never" class="mb15">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold;"><i class="el-icon-office-building" style="color: #67C23A; margin-right: 5px;" />提前住宿分配安排</span>
|
||||
</div>
|
||||
<div v-if="previewData.hasAccommodation">
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="宿舍楼栋">{{ previewData.dormBuilding || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="房间号">{{ previewData.dormRoom || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="床位号">
|
||||
<span style="font-weight: bold; color: #409EFF;">{{ previewData.bedNo || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="住宿状态">
|
||||
<el-tag :type="previewData.accommodationStatus === 1 ? 'success' : 'info'" size="small">
|
||||
{{ previewData.accommodationStatusLabel }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<p style="margin-top: 10px; font-size: 12px; color: #67C23A;">
|
||||
<i class="el-icon-success" /> 管理员已提前分配宿舍与床位,报到后可直接前往入住。
|
||||
</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<el-empty description="当前尚未分配宿舍" :image-size="70">
|
||||
<span style="font-size: 12px; color: #909399;">如需住宿,请管理员在【开班计划】参训人员名单中进行住宿安排</span>
|
||||
</el-empty>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 对应计划课程表 -->
|
||||
<el-card shadow="never" class="mb15">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold;"><i class="el-icon-date" style="color: #E6A23C; margin-right: 5px;" />本期培训课程表安排</span>
|
||||
</div>
|
||||
|
||||
<div v-if="flattenedLessons && flattenedLessons.length > 0">
|
||||
<el-table :data="flattenedLessons" size="small" border stripe>
|
||||
<el-table-column type="index" label="序号" width="55" align="center" />
|
||||
<el-table-column prop="date" label="上课日期" width="110" align="center" />
|
||||
<el-table-column label="上课时间" width="140" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatTimeRange(scope.row.startTime, scope.row.endTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="courseName" label="课程名称" min-width="160" show-overflow-tooltip />
|
||||
<el-table-column prop="teacherName" label="授课教师" width="110" align="center" />
|
||||
<el-table-column label="上课教室/场地" min-width="140" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="mini" :type="scope.row.venueType === 2 ? 'warning' : 'primary'">
|
||||
{{ scope.row.venueType === 2 ? '实训场地' : '教室' }}
|
||||
</el-tag>
|
||||
<span style="margin-left: 6px;">{{ scope.row.venueName || scope.row.classRoomName || scope.row.siteName || '-' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="100" show-overflow-tooltip />
|
||||
</el-table>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p style="text-align: center; color: #909399; margin: 20px 0;">当前开班计划尚未编排课次</p>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 确认报到操作栏 -->
|
||||
<el-card shadow="never" style="text-align: center; background-color: #F8F9FB;">
|
||||
<div style="margin: 10px 0;">
|
||||
<span v-if="previewData.isReported" style="margin-right: 15px; color: #67C23A; font-weight: bold;">
|
||||
<i class="el-icon-check" /> 学员已完成报到
|
||||
</span>
|
||||
<span v-else style="margin-right: 15px; color: #E6A23C; font-weight: bold;">
|
||||
<i class="el-icon-info" /> 请确认学员信息无误后办理报到
|
||||
</span>
|
||||
|
||||
<el-button
|
||||
type="success"
|
||||
size="medium"
|
||||
icon="el-icon-circle-check"
|
||||
:loading="confirmLoading"
|
||||
:disabled="previewData.isReported"
|
||||
@click="handleConfirmReport"
|
||||
>
|
||||
{{ previewData.isReported ? '已完成报到' : '一键确认报到' }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="previewData.isReported"
|
||||
type="primary"
|
||||
plain
|
||||
size="medium"
|
||||
icon="el-icon-refresh"
|
||||
@click="handleLookup"
|
||||
>
|
||||
刷新最新状态
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- Tab 2: 模拟课堂打卡 -->
|
||||
<el-tab-pane label="模拟课堂打卡 (Mock Class Attendance)" name="mockAttendance">
|
||||
<el-card class="box-card mb15" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span style="font-weight: bold; font-size: 15px;">
|
||||
<i class="el-icon-bell" style="margin-right: 6px; color: #E6A23C;" />模拟设备课堂考勤刷卡
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
title="考勤业务规则说明"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
description="1. 课堂打卡与入学报到严格区分:打卡不会改变入学报到状态,报到也不会自动生成课堂考勤记录。
|
||||
2. 只有已报到的学员才能进行课堂打卡;未完成入学报到的学员打卡将被系统拒绝。
|
||||
3. 此接口为受控 Mock,打卡记录带 MOCK 标识,便于测试溯源。"
|
||||
show-icon
|
||||
class="mb15"
|
||||
/>
|
||||
|
||||
<el-form ref="mockForm" :model="mockForm" :rules="mockRules" label-width="120px" style="max-width: 600px;">
|
||||
<el-form-item label="学员学号" prop="studentId">
|
||||
<el-input v-model="mockForm.studentId" placeholder="请输入学员学号,如 S101" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="目标课次标识" prop="lessonId">
|
||||
<el-input-number v-model="mockForm.lessonId" :min="1" placeholder="课次ID,如 102" style="width: 100%;" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="打卡动作" prop="actionType">
|
||||
<el-radio-group v-model="mockForm.actionType">
|
||||
<el-radio label="SIGN_IN">课堂签到 (Sign In)</el-radio>
|
||||
<el-radio label="SIGN_OUT">课堂签退 (Sign Out)</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模拟设备编号">
|
||||
<el-input v-model="mockForm.deviceSn" placeholder="MOCK-BOARD-01" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" :loading="mockLoading" @click="handleMockSubmit">提交模拟打卡</el-button>
|
||||
<el-button @click="resetMockForm">重置表单</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 打卡结果回显 -->
|
||||
<div v-if="mockResult" style="margin-top: 20px;">
|
||||
<el-divider content-position="left">打卡响应结果</el-divider>
|
||||
<el-alert
|
||||
:title="mockResult.message || '打卡操作完成'"
|
||||
type="success"
|
||||
:closable="false"
|
||||
show-icon
|
||||
class="mb15"
|
||||
/>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="打卡记录ID">{{ mockResult.checkinId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="学员姓名">{{ mockResult.studentName || mockResult.studentId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="培训班ID">{{ mockResult.planId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="课程名称">{{ mockResult.courseName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="动作类型">
|
||||
<el-tag size="mini">{{ mockResult.actionType === 'SIGN_IN' ? '签到' : '签退' }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="出勤状态">
|
||||
<el-tag size="mini" type="success">{{ formatAttendanceStatus(mockResult.attendanceStatus) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="签到时间">{{ parseTime(mockResult.signInTime) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="签退时间">{{ parseTime(mockResult.signOutTime) || '-' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { lookupTerminal, confirmTerminal, mockAttendance } from '@/api/train/terminal'
|
||||
|
||||
export default {
|
||||
name: 'TrainTerminal',
|
||||
data() {
|
||||
return {
|
||||
activeTab: 'reportTerminal',
|
||||
searchForm: {
|
||||
studentId: '',
|
||||
planId: null
|
||||
},
|
||||
lookupLoading: false,
|
||||
confirmLoading: false,
|
||||
previewData: null,
|
||||
|
||||
// Mock 打卡表单
|
||||
mockForm: {
|
||||
studentId: '',
|
||||
lessonId: null,
|
||||
actionType: 'SIGN_IN',
|
||||
deviceSn: 'MOCK-BOARD-01'
|
||||
},
|
||||
mockRules: {
|
||||
studentId: [{ required: true, message: '请输入学员学号', trigger: 'blur' }],
|
||||
lessonId: [{ required: true, message: '请输入目标课次ID', trigger: 'blur' }],
|
||||
actionType: [{ required: true, message: '请选择打卡动作', trigger: 'change' }]
|
||||
},
|
||||
mockLoading: false,
|
||||
mockResult: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
flattenedLessons() {
|
||||
if (!this.previewData || !this.previewData.timetable || !this.previewData.timetable.days) {
|
||||
return []
|
||||
}
|
||||
const list = []
|
||||
this.previewData.timetable.days.forEach(day => {
|
||||
if (day.lessons && day.lessons.length > 0) {
|
||||
day.lessons.forEach(lesson => {
|
||||
list.push({
|
||||
...lesson,
|
||||
date: day.date
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
return list
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 检查是否有路由参数预填
|
||||
if (this.$route.query.studentId) {
|
||||
this.searchForm.studentId = this.$route.query.studentId
|
||||
if (this.$route.query.planId) {
|
||||
this.searchForm.planId = this.$route.query.planId
|
||||
}
|
||||
this.handleLookup()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleLookup() {
|
||||
if (!this.searchForm.studentId || !this.searchForm.studentId.trim()) {
|
||||
this.$modal.msgWarning('请输入学员学号进行查询')
|
||||
return
|
||||
}
|
||||
this.lookupLoading = true
|
||||
lookupTerminal({
|
||||
studentId: this.searchForm.studentId.trim(),
|
||||
planId: this.searchForm.planId || undefined
|
||||
}).then(res => {
|
||||
this.previewData = res.data || null
|
||||
if (this.previewData && this.previewData.planId) {
|
||||
this.searchForm.planId = this.previewData.planId
|
||||
}
|
||||
if (this.previewData && this.previewData.message) {
|
||||
this.$modal.msgSuccess(this.previewData.message)
|
||||
}
|
||||
}).catch(err => {
|
||||
const msg = (err && (err.msg || err.message)) || '查询失败'
|
||||
this.$modal.msgError(msg)
|
||||
}).finally(() => {
|
||||
this.lookupLoading = false
|
||||
})
|
||||
},
|
||||
handlePlanChange(val) {
|
||||
this.searchForm.planId = val
|
||||
this.handleLookup()
|
||||
},
|
||||
handleReset() {
|
||||
this.searchForm = {
|
||||
studentId: '',
|
||||
planId: null
|
||||
}
|
||||
this.previewData = null
|
||||
},
|
||||
handleConfirmReport() {
|
||||
if (!this.previewData) return
|
||||
this.$confirm(`确认学员【${this.previewData.studentName || this.previewData.studentId}】办理报到?`, '报到确认', {
|
||||
confirmButtonText: '确定报到',
|
||||
cancelButtonText: '取消',
|
||||
type: 'info'
|
||||
}).then(() => {
|
||||
this.confirmLoading = true
|
||||
return confirmTerminal({
|
||||
enrollmentId: this.previewData.enrollmentId,
|
||||
studentId: this.previewData.studentId,
|
||||
planId: this.previewData.planId,
|
||||
reportDeviceSn: 'TERMINAL-MOCK-01',
|
||||
verifyNote: '报到机自主确认'
|
||||
})
|
||||
}).then(res => {
|
||||
this.$modal.msgSuccess('报到确认成功!')
|
||||
this.previewData = res.data || this.previewData
|
||||
}).catch(err => {
|
||||
if (err !== 'cancel') {
|
||||
const msg = (err && (err.msg || err.message)) || '报到确认失败'
|
||||
this.$modal.msgError(msg)
|
||||
}
|
||||
}).finally(() => {
|
||||
this.confirmLoading = false
|
||||
})
|
||||
},
|
||||
handleMockSubmit() {
|
||||
this.$refs.mockForm.validate(valid => {
|
||||
if (!valid) return
|
||||
this.mockLoading = true
|
||||
mockAttendance(this.mockForm).then(res => {
|
||||
this.mockResult = res.data || null
|
||||
this.$modal.msgSuccess('模拟打卡成功')
|
||||
}).catch(err => {
|
||||
const msg = (err && (err.msg || err.message)) || '模拟打卡失败'
|
||||
this.$modal.msgError(msg)
|
||||
}).finally(() => {
|
||||
this.mockLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
resetMockForm() {
|
||||
this.$refs.mockForm.resetFields()
|
||||
this.mockResult = null
|
||||
},
|
||||
formatGender(val) {
|
||||
if (val === 1 || val === '1' || val === '男') return '男'
|
||||
if (val === 2 || val === '2' || val === '女') return '女'
|
||||
return '未知'
|
||||
},
|
||||
statusTagType(val) {
|
||||
const map = { 0: 'info', 1: 'success', 2: 'warning', 3: 'danger', 4: 'primary' }
|
||||
return map[val] || 'info'
|
||||
},
|
||||
formatTimeRange(start, end) {
|
||||
if (!start || !end) return '-'
|
||||
const s = start.length > 16 ? start.substring(11, 16) : start
|
||||
const e = end.length > 16 ? end.substring(11, 16) : end
|
||||
return `${s} ~ ${e}`
|
||||
},
|
||||
formatAttendanceStatus(status) {
|
||||
const map = { 1: '正常出勤', 2: '迟到', 3: '早退', 4: '缺勤', 5: '请假' }
|
||||
return map[status] || '已打卡'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.terminal-container {
|
||||
padding: 20px;
|
||||
}
|
||||
.mb15 {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user