2026-01-21 18:24:36 +08:00

233 lines
4.4 KiB
Vue

<template>
<div class="rewards-punishments-container">
<!-- 标题栏 -->
<div class="rewards-header">
<span class="header-title">近期奖惩</span>
<div class="filter-tabs">
<div
v-for="tab in filterTabs"
:key="tab.value"
class="filter-tab"
:class="{ active: activeFilter === tab.value }"
@click="activeFilter = tab.value"
>
{{ tab.label }}
</div>
</div>
</div>
<!-- 时间线列表 -->
<div class="timeline-container">
<div class="timeline-content">
<div class="timeline-line"></div>
<div class="timeline-items">
<div v-for="(item, index) in filteredList" :key="index" class="timeline-item">
<div class="timeline-dot" :class="item.type"></div>
<div class="timeline-card">
<div class="card-type" :class="item.type">{{ item.typeText }}</div>
<div class="card-description">{{ item.content }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
defineOptions({ name: 'RecentRewardsPunishments' })
interface RewardPunishmentItem {
date?: string // 日期(可选)
type: 'reward' | 'danger'
typeText: string // 类型文本(表扬奖励/警告等)
content: string // 内容
}
// 过滤标签
const filterTabs = [
{ label: '全部', value: 'all' },
{ label: '奖励记录', value: 'reward' },
{ label: '惩罚记录', value: 'punishment' }
]
const activeFilter = ref<string>('all')
// 数据列表 - 使用 ref 存储
const listData = ref<RewardPunishmentItem[]>([])
// 过滤后的列表
const filteredList = computed(() => {
if (activeFilter.value === 'all') {
return listData.value
} else if (activeFilter.value === 'reward') {
return listData.value.filter((item) => item.type === 'reward')
} else {
return listData.value.filter((item) => item.type === 'danger')
}
})
// 可以通过 props 接收外部数据
const props = withDefaults(
defineProps<{
data?: RewardPunishmentItem[]
}>(),
{
data: () => []
}
)
// 监听 props 数据变化
watch(
() => props.data,
(newData) => {
if (newData && newData.length > 0) {
listData.value = newData
}
},
{ immediate: true, deep: true }
)
</script>
<style scoped lang="scss">
.rewards-punishments-container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
border: 1px solid rgba(56, 102, 141, 0.5);
border-radius: 8px;
padding: 20px;
}
// 标题栏
.rewards-header {
flex-shrink: 0;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 14px;
}
.header-title {
font-size: 2vh;
font-weight: bold;
color: #ffffff;
}
.filter-tabs {
display: flex;
gap: 8px;
}
.filter-tab {
padding: 6px 12px;
font-size: 12px;
color: rgba(255, 255, 255, 0.85);
background: rgba(56, 102, 141, 0.2);
border-radius: 4px;
&.active {
background: #37599d;
color: #ffffff;
}
}
// 时间线容器
.timeline-container {
flex: 1 1 0;
min-height: 0;
overflow-y: auto;
padding-left: 10px;
&::-webkit-scrollbar {
width: 0px;
}
}
.timeline-content {
position: relative;
padding-bottom: 8px;
}
// 时间线
.timeline-line {
position: absolute;
left: -2px;
top: 0;
bottom: 0;
width: 2px;
background: #5e7fef;
}
.timeline-items {
position: relative;
margin-left: 2px;
}
// 时间线项
.timeline-item {
position: relative;
display: flex;
align-items: flex-start;
margin-bottom: 10px;
}
// 时间线标记点
.timeline-dot {
position: absolute;
left: -8px;
top: 50%;
transform: translateY(-50%);
width: 10px;
height: 10px;
border-radius: 50%;
background: rgba(3, 173, 252, 0.8);
z-index: 1;
&.reward {
background: #10b981;
border-color: #10b981;
}
&.punishment {
background: #ff0000;
border-color: #ff0000;
}
}
// 事件卡片
.timeline-card {
flex: 1;
background: rgba(56, 102, 141, 0.15);
border: 1px solid rgba(56, 102, 141, 0.3);
border-radius: 4px;
padding: 8px 12px;
margin-left: 1px;
}
.card-type {
font-size: 1.8vh;
font-weight: 500;
margin-bottom: 4px;
color: rgba(255, 255, 255, 0.9);
&.reward {
color: #10b981;
}
&.punishment {
color: #ff0000;
}
}
.card-description {
font-size: 1.4vh;
color: rgba(255, 255, 255, 0.7);
line-height: 1.5;
}
</style>