|
|
|
|
@ -7,18 +7,11 @@
|
|
|
|
|
|
|
|
|
|
<!-- 课程选择区 -->
|
|
|
|
|
<div class="filter-bar">
|
|
|
|
|
<el-select v-model="filters.course" placeholder="选择课程" size="default" style="width: 200px">
|
|
|
|
|
<el-option label="高等数学A" value="math_a" />
|
|
|
|
|
<el-option label="计算机导论" value="cs_intro" />
|
|
|
|
|
<el-option label="大学英语B" value="eng_b" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-select v-model="filters.teacher" placeholder="教师" clearable size="default" style="width: 140px">
|
|
|
|
|
<el-option label="王教授" value="wang" />
|
|
|
|
|
<el-option label="张教授" value="zhang" />
|
|
|
|
|
<el-option label="李老师" value="li" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<el-date-picker v-model="filters.date" type="date" placeholder="选择日期" size="default" />
|
|
|
|
|
<el-button type="primary" :icon="Search">查询</el-button>
|
|
|
|
|
<el-input v-model="filters.courseName" placeholder="搜索课程" clearable size="default" style="width: 200px" />
|
|
|
|
|
<el-input v-model="filters.teacherName" placeholder="搜索教师" clearable size="default" style="width: 180px" />
|
|
|
|
|
<el-date-picker v-model="filters.date" type="date" placeholder="选择日期" size="default" value-format="YYYY-MM-DD" />
|
|
|
|
|
<el-button type="primary" :icon="Search" :loading="loading" @click="fetchData">查询</el-button>
|
|
|
|
|
<el-button :icon="RefreshRight" @click="handleReset">重置</el-button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 行为统计图表 -->
|
|
|
|
|
@ -32,7 +25,7 @@
|
|
|
|
|
<div ref="pieChartRef" class="chart-body"></div>
|
|
|
|
|
<div class="behavior-legend">
|
|
|
|
|
<span v-for="b in behaviors" :key="b.name" class="legend-dot" :style="{ background: b.color }">
|
|
|
|
|
{{ b.name }} {{ b.percent }}%
|
|
|
|
|
{{ b.name }} {{ behaviorPercent(b) }}%
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
@ -50,39 +43,52 @@
|
|
|
|
|
<div class="image-section">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h3>行为标记图片</h3>
|
|
|
|
|
<el-radio-group v-model="imageFilter" size="small">
|
|
|
|
|
<el-radio-button value="all">全部</el-radio-button>
|
|
|
|
|
<el-radio-button value="专注">专注</el-radio-button>
|
|
|
|
|
<el-radio-button value="举手">举手</el-radio-button>
|
|
|
|
|
<el-radio-button value="低头">低头</el-radio-button>
|
|
|
|
|
<el-radio-button value="交谈">交谈</el-radio-button>
|
|
|
|
|
<el-radio-group v-model="imageFilter" size="small" @change="onFilterChange">
|
|
|
|
|
<el-radio-button value="">全部</el-radio-button>
|
|
|
|
|
<el-radio-button
|
|
|
|
|
v-for="bt in behaviorTypes"
|
|
|
|
|
:key="bt.id"
|
|
|
|
|
:value="bt.id"
|
|
|
|
|
>{{ bt.typeName }}</el-radio-button>
|
|
|
|
|
</el-radio-group>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="image-grid">
|
|
|
|
|
<div v-if="imageRecords.length" class="image-grid">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(img, index) in filteredImages"
|
|
|
|
|
:key="index"
|
|
|
|
|
v-for="(img, index) in imageRecords"
|
|
|
|
|
:key="img.id || index"
|
|
|
|
|
class="image-card"
|
|
|
|
|
@click="previewImage(img)"
|
|
|
|
|
>
|
|
|
|
|
<div class="image-placeholder" :style="{ borderColor: getBehaviorColor(img.type) }">
|
|
|
|
|
<el-icon :size="32" :color="getBehaviorColor(img.type)">
|
|
|
|
|
<component :is="getBehaviorIcon(img.type)" />
|
|
|
|
|
<div class="image-placeholder" :style="{ borderColor: getTypeColor(img.behaviorTypeId) }">
|
|
|
|
|
<img v-if="img.snapshotUrl" :src="img.snapshotUrl" alt="" class="snapshot-img" />
|
|
|
|
|
<el-icon v-else :size="32" :color="getTypeColor(img.behaviorTypeId)">
|
|
|
|
|
<PictureFilled />
|
|
|
|
|
</el-icon>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="image-info">
|
|
|
|
|
<span class="image-time">{{ img.time }}</span>
|
|
|
|
|
<el-tag :type="getBehaviorTagType(img.type)" size="small">{{ img.type }}</el-tag>
|
|
|
|
|
<span class="image-time">{{ img.behaviorTime }}</span>
|
|
|
|
|
<el-tag :color="getTypeColor(img.behaviorTypeId)" size="small" effect="dark">
|
|
|
|
|
{{ img.behaviorTypeName }}
|
|
|
|
|
</el-tag>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="image-check" @click.stop>
|
|
|
|
|
<el-checkbox v-model="img.checked" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="image-actions">
|
|
|
|
|
<el-pagination small background layout="prev, pager, next" :total="40" />
|
|
|
|
|
<el-empty v-else description="暂无数据" :image-size="80" />
|
|
|
|
|
|
|
|
|
|
<div v-if="imageRecords.length" class="image-actions">
|
|
|
|
|
<el-pagination
|
|
|
|
|
small
|
|
|
|
|
background
|
|
|
|
|
layout="prev, pager, next"
|
|
|
|
|
:total="pagination.total"
|
|
|
|
|
:current-page="pagination.current"
|
|
|
|
|
:page-size="pagination.size"
|
|
|
|
|
@current-change="onPageChange"
|
|
|
|
|
/>
|
|
|
|
|
<el-button v-if="hasChecked" type="primary" size="small" :icon="Download" @click="batchDownload">
|
|
|
|
|
批量下载 ({{ checkedCount }}张)
|
|
|
|
|
</el-button>
|
|
|
|
|
@ -93,9 +99,12 @@
|
|
|
|
|
<el-dialog v-model="previewVisible" title="图片预览" width="600px">
|
|
|
|
|
<div class="preview-container">
|
|
|
|
|
<div class="preview-placeholder">
|
|
|
|
|
<el-icon :size="64" color="#d9d9d9"><PictureFilled /></el-icon>
|
|
|
|
|
<p>图片预览区域</p>
|
|
|
|
|
<span class="preview-meta">{{ currentPreview?.time }} | {{ currentPreview?.type }}</span>
|
|
|
|
|
<img v-if="currentPreview?.snapshotUrl" :src="currentPreview.snapshotUrl" alt="" class="preview-img" />
|
|
|
|
|
<template v-else>
|
|
|
|
|
<el-icon :size="64" color="#d9d9d9"><PictureFilled /></el-icon>
|
|
|
|
|
<p>暂无图片</p>
|
|
|
|
|
</template>
|
|
|
|
|
<span class="preview-meta">{{ currentPreview?.behaviorTime }} | {{ currentPreview?.behaviorTypeName }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</el-dialog>
|
|
|
|
|
@ -106,64 +115,53 @@
|
|
|
|
|
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
|
|
import * as echarts from 'echarts'
|
|
|
|
|
import { getBehaviorTypesWithStats, getBehaviorTimePeriod, getBehaviorRecords, getBehaviorTypes } from '@/api/behavior'
|
|
|
|
|
|
|
|
|
|
const filters = reactive({
|
|
|
|
|
course: 'math_a',
|
|
|
|
|
teacher: '',
|
|
|
|
|
courseName: '',
|
|
|
|
|
teacherName: '',
|
|
|
|
|
date: null
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const imageFilter = ref('all')
|
|
|
|
|
const imageFilter = ref('')
|
|
|
|
|
|
|
|
|
|
// 行为类型列表(用于筛选按钮和颜色映射)
|
|
|
|
|
const behaviorTypes = ref([])
|
|
|
|
|
|
|
|
|
|
const behaviors = ref([
|
|
|
|
|
{ name: '专注听讲', percent: 65, color: '#52c41a' },
|
|
|
|
|
{ name: '举手互动', percent: 15, color: '#1890ff' },
|
|
|
|
|
{ name: '低头书写', percent: 12, color: '#722ed1' },
|
|
|
|
|
{ name: '交谈讨论', percent: 5, color: '#faad14' },
|
|
|
|
|
{ name: '其他', percent: 3, color: '#bfbfbf' }
|
|
|
|
|
])
|
|
|
|
|
// 行为分布数据(从接口获取)
|
|
|
|
|
const behaviors = ref([])
|
|
|
|
|
const loading = ref(false)
|
|
|
|
|
|
|
|
|
|
const pieChartRef = ref(null)
|
|
|
|
|
const barChartRef = ref(null)
|
|
|
|
|
let pieChart = null
|
|
|
|
|
let barChart = null
|
|
|
|
|
|
|
|
|
|
// 模拟图片数据
|
|
|
|
|
const images = ref([
|
|
|
|
|
{ time: '08:15:32', type: '专注', checked: false },
|
|
|
|
|
{ time: '08:18:05', type: '举手', checked: false },
|
|
|
|
|
{ time: '08:22:41', type: '专注', checked: false },
|
|
|
|
|
{ time: '08:30:18', type: '低头', checked: false },
|
|
|
|
|
{ time: '08:35:56', type: '专注', checked: false },
|
|
|
|
|
{ time: '08:42:10', type: '交谈', checked: false },
|
|
|
|
|
{ time: '08:48:33', type: '举手', checked: false },
|
|
|
|
|
{ time: '08:55:20', type: '专注', checked: false }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const filteredImages = computed(() => {
|
|
|
|
|
if (imageFilter.value === 'all') return images.value
|
|
|
|
|
return images.value.filter(i => i.type === imageFilter.value)
|
|
|
|
|
// 图片记录列表
|
|
|
|
|
const imageRecords = ref([])
|
|
|
|
|
const pagination = reactive({
|
|
|
|
|
current: 1,
|
|
|
|
|
size: 10,
|
|
|
|
|
total: 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const checkedCount = computed(() => images.value.filter(i => i.checked).length)
|
|
|
|
|
const checkedCount = computed(() => imageRecords.value.filter(i => i.checked).length)
|
|
|
|
|
const hasChecked = computed(() => checkedCount.value > 0)
|
|
|
|
|
|
|
|
|
|
const previewVisible = ref(false)
|
|
|
|
|
const currentPreview = ref(null)
|
|
|
|
|
|
|
|
|
|
const getBehaviorColor = (type) => {
|
|
|
|
|
const map = { '专注': '#52c41a', '举手': '#1890ff', '低头': '#722ed1', '交谈': '#faad14' }
|
|
|
|
|
return map[type] || '#bfbfbf'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getBehaviorTagType = (type) => {
|
|
|
|
|
const map = { '专注': 'success', '举手': '', '低头': 'info', '交谈': 'warning' }
|
|
|
|
|
return map[type] || 'info'
|
|
|
|
|
/** 根据行为类型 ID 获取对应颜色 */
|
|
|
|
|
const getTypeColor = (behaviorTypeId) => {
|
|
|
|
|
const bt = behaviorTypes.value.find(t => t.id === behaviorTypeId)
|
|
|
|
|
return bt ? bt.color : '#bfbfbf'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getBehaviorIcon = (type) => {
|
|
|
|
|
const map = { '专注': 'View', '举手': 'Pointer', '低头': 'Reading', '交谈': 'ChatDotRound' }
|
|
|
|
|
return map[type] || 'QuestionFilled'
|
|
|
|
|
/** 计算单个行为占比 */
|
|
|
|
|
const behaviorPercent = (b) => {
|
|
|
|
|
if (!behaviors.value.length) return 0
|
|
|
|
|
const total = behaviors.value.reduce((sum, item) => sum + (item.count || 0), 0)
|
|
|
|
|
return total > 0 ? ((b.count / total) * 100).toFixed(1) : 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const previewImage = (img) => {
|
|
|
|
|
@ -175,21 +173,188 @@ const batchDownload = () => {
|
|
|
|
|
ElMessage.success(`正在下载 ${checkedCount.value} 张图片...`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 获取行为类型列表 */
|
|
|
|
|
const fetchBehaviorTypes = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await getBehaviorTypes()
|
|
|
|
|
if (res && res.data) {
|
|
|
|
|
behaviorTypes.value = res.data
|
|
|
|
|
imageFilter.value = ''
|
|
|
|
|
}
|
|
|
|
|
} catch { /* 错误已在拦截器统一处理 */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 获取行为标记图片记录 */
|
|
|
|
|
const fetchRecords = async (page = 1) => {
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
current: page,
|
|
|
|
|
size: pagination.size,
|
|
|
|
|
courseName: filters.courseName || undefined,
|
|
|
|
|
teacherName: filters.teacherName || undefined,
|
|
|
|
|
attDate: filters.date || undefined,
|
|
|
|
|
behaviorTypeId: imageFilter.value || undefined
|
|
|
|
|
}
|
|
|
|
|
const res = await getBehaviorRecords(params)
|
|
|
|
|
if (res && res.data) {
|
|
|
|
|
imageRecords.value = (res.data.records || []).map(r => ({
|
|
|
|
|
...r,
|
|
|
|
|
checked: false
|
|
|
|
|
}))
|
|
|
|
|
pagination.current = res.data.current
|
|
|
|
|
pagination.total = res.data.total
|
|
|
|
|
pagination.size = res.data.size
|
|
|
|
|
}
|
|
|
|
|
} catch { /* 错误已在拦截器统一处理 */ }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 重置筛选条件 */
|
|
|
|
|
const handleReset = () => {
|
|
|
|
|
filters.courseName = ''
|
|
|
|
|
filters.teacherName = ''
|
|
|
|
|
filters.date = null
|
|
|
|
|
imageFilter.value = ''
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchData()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 查询按钮 */
|
|
|
|
|
const fetchData = async () => {
|
|
|
|
|
loading.value = true
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
try {
|
|
|
|
|
const params = {
|
|
|
|
|
courseName: filters.courseName || undefined,
|
|
|
|
|
teacherName: filters.teacherName || undefined,
|
|
|
|
|
attDate: filters.date || undefined
|
|
|
|
|
}
|
|
|
|
|
const [res1, res2] = await Promise.all([
|
|
|
|
|
getBehaviorTypesWithStats(params),
|
|
|
|
|
getBehaviorTimePeriod(params)
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
// 处理行为分布占比数据
|
|
|
|
|
if (res1 && res1.data) {
|
|
|
|
|
behaviors.value = res1.data.map(item => ({
|
|
|
|
|
id: item.id,
|
|
|
|
|
typeCode: item.typeCode,
|
|
|
|
|
name: item.typeName,
|
|
|
|
|
color: item.color,
|
|
|
|
|
count: item.count,
|
|
|
|
|
category: item.category,
|
|
|
|
|
description: item.description
|
|
|
|
|
}))
|
|
|
|
|
updatePieChart()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 处理各行为时段分布数据
|
|
|
|
|
if (res2 && res2.data) {
|
|
|
|
|
updateBarChart(res2.data)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 同时查询图片记录
|
|
|
|
|
await fetchRecords(1)
|
|
|
|
|
} catch {
|
|
|
|
|
// 错误已在拦截器中统一处理
|
|
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 筛选条件变化时重新查询 */
|
|
|
|
|
const onFilterChange = () => {
|
|
|
|
|
pagination.current = 1
|
|
|
|
|
fetchRecords(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 分页切换 */
|
|
|
|
|
const onPageChange = (page) => {
|
|
|
|
|
fetchRecords(page)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 更新饼图数据 */
|
|
|
|
|
const updatePieChart = () => {
|
|
|
|
|
if (!pieChart) return
|
|
|
|
|
const total = behaviors.value.reduce((sum, b) => sum + (b.count || 0), 0)
|
|
|
|
|
pieChart.setOption({
|
|
|
|
|
series: [{
|
|
|
|
|
data: behaviors.value.map(b => ({
|
|
|
|
|
value: b.count,
|
|
|
|
|
name: b.name,
|
|
|
|
|
itemStyle: { color: b.color }
|
|
|
|
|
}))
|
|
|
|
|
}],
|
|
|
|
|
tooltip: {
|
|
|
|
|
trigger: 'item',
|
|
|
|
|
formatter: (params) => {
|
|
|
|
|
const pct = total > 0 ? ((params.value / total) * 100).toFixed(1) : 0
|
|
|
|
|
return `${params.name}: ${params.value} (${pct}%)`
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** 更新柱状图数据 */
|
|
|
|
|
const updateBarChart = (data) => {
|
|
|
|
|
if (!barChart || !data.length) return
|
|
|
|
|
|
|
|
|
|
// x 轴:各时段标签
|
|
|
|
|
const xData = data.map(d => d.timeSlot)
|
|
|
|
|
|
|
|
|
|
// 收集所有行为类型名称及对应颜色(去重,按首次出现顺序)
|
|
|
|
|
const nameSet = new Set()
|
|
|
|
|
const nameList = []
|
|
|
|
|
const colorMap = {}
|
|
|
|
|
data.forEach(slot => {
|
|
|
|
|
(slot.data || []).forEach(item => {
|
|
|
|
|
if (!nameSet.has(item.name)) {
|
|
|
|
|
nameSet.add(item.name)
|
|
|
|
|
nameList.push(item.name)
|
|
|
|
|
colorMap[item.name] = item.color
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// 构建 series:每个行为类型一根柱子
|
|
|
|
|
const series = nameList.map((name, idx) => ({
|
|
|
|
|
name,
|
|
|
|
|
type: 'bar',
|
|
|
|
|
stack: 'total',
|
|
|
|
|
data: data.map(slot => {
|
|
|
|
|
const found = (slot.data || []).find(item => item.name === name)
|
|
|
|
|
return found ? found.value : 0
|
|
|
|
|
}),
|
|
|
|
|
color: colorMap[name],
|
|
|
|
|
itemStyle: idx === 0 ? { borderRadius: [4, 4, 0, 0] } : {}
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
barChart.setOption({
|
|
|
|
|
grid: { bottom: 70 },
|
|
|
|
|
xAxis: { data: xData },
|
|
|
|
|
legend: {
|
|
|
|
|
data: nameList,
|
|
|
|
|
bottom: 0,
|
|
|
|
|
type: 'scroll'
|
|
|
|
|
},
|
|
|
|
|
series
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const initCharts = () => {
|
|
|
|
|
// 饼图
|
|
|
|
|
if (pieChartRef.value) {
|
|
|
|
|
pieChart = echarts.init(pieChartRef.value)
|
|
|
|
|
pieChart.setOption({
|
|
|
|
|
tooltip: { trigger: 'item', formatter: '{b}: {c}%' },
|
|
|
|
|
tooltip: { trigger: 'item' },
|
|
|
|
|
series: [{
|
|
|
|
|
type: 'pie',
|
|
|
|
|
radius: ['55%', '80%'],
|
|
|
|
|
radius: ['45%', '80%'],
|
|
|
|
|
center: ['50%', '50%'],
|
|
|
|
|
avoidLabelOverlap: false,
|
|
|
|
|
itemStyle: { borderRadius: 4, borderColor: '#fff', borderWidth: 2 },
|
|
|
|
|
label: { show: false },
|
|
|
|
|
emphasis: { label: { show: true, fontSize: 16, fontWeight: 'bold' } },
|
|
|
|
|
data: behaviors.value.map(b => ({ value: b.percent, name: b.name, itemStyle: { color: b.color } }))
|
|
|
|
|
data: []
|
|
|
|
|
}]
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
@ -214,6 +379,8 @@ const initCharts = () => {
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
initCharts()
|
|
|
|
|
fetchBehaviorTypes()
|
|
|
|
|
fetchData()
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
|
pieChart?.resize()
|
|
|
|
|
barChart?.resize()
|
|
|
|
|
@ -268,18 +435,19 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
|
|
.legend-dot {
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
color: #525252;
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
gap: 4px;
|
|
|
|
|
|
|
|
|
|
&::before {
|
|
|
|
|
content: '';
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
background: inherit;
|
|
|
|
|
}
|
|
|
|
|
padding: 2px 5px;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
// &::before {
|
|
|
|
|
// content: '';
|
|
|
|
|
// width: 8px;
|
|
|
|
|
// height: 8px;
|
|
|
|
|
// border-radius: 50%;
|
|
|
|
|
// background: inherit;
|
|
|
|
|
// }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.image-section {
|
|
|
|
|
@ -330,6 +498,13 @@ onUnmounted(() => {
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
border-bottom: 3px solid #d9d9d9;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
.snapshot-img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: cover;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.image-info {
|
|
|
|
|
@ -377,6 +552,13 @@ onUnmounted(() => {
|
|
|
|
|
gap: 8px;
|
|
|
|
|
color: #bfbfbf;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
|
|
|
|
.preview-img {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
object-fit: contain;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.preview-meta {
|
|
|
|
|
|