Files
xh-medical-sign-web/clinical-web/src/views/workbench/home/components/DocumentRankingPanel.vue
2026-08-27 17:08:51 +08:00

248 lines
5.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed } from 'vue'
import type { HomeDocumentRanking, HomeRankingPeriod } from '../types'
const props = defineProps<{
rows: HomeDocumentRanking[]
period: HomeRankingPeriod
}>()
const emit = defineEmits<{
'update:period': [period: HomeRankingPeriod]
manage: []
}>()
const periodOptions: Array<{ label: string; value: HomeRankingPeriod }> = [
{ label: '近7天', value: 7 },
{ label: '近14天', value: 14 },
{ label: '近30天', value: 30 },
]
const rankColors = ['#0e6e8c', '#18a0c0', '#0f9d6c', '#d9821f', '#7a4dbb', '#d0534f']
const maxCount = computed(() => Math.max(...props.rows.map((row) => row.signedCount), 1))
function getBarWidth(row: HomeDocumentRanking) {
return `${Math.max((row.signedCount / maxCount.value) * 100, 1.5)}%`
}
function getRankColor(index: number) {
return rankColors[index % rankColors.length]
}
</script>
<template>
<section class="home-panel ranking-panel">
<div class="panel-heading">
<h2>Top10 文档签署排名</h2>
<span class="heading-spacer" />
<div class="period-switch" role="group" aria-label="排名时间范围">
<button
v-for="option in periodOptions"
:key="option.value"
type="button"
class="period-button"
:class="{ 'period-button--active': period === option.value }"
@click="emit('update:period', option.value)"
>
{{ option.label }}
</button>
</div>
<button type="button" class="panel-more" @click="emit('manage')">管理 </button>
</div>
<div v-if="rows.length" class="ranking-chart" role="list" aria-label="文档签署排名">
<div
v-for="(row, index) in rows"
:key="row.id"
class="ranking-row"
role="listitem"
:title="`${row.documentName}${row.signedCount} 次(${row.department}`"
>
<span class="ranking-number" :style="{ color: getRankColor(index) }">{{ index + 1 }}</span>
<span class="ranking-name">{{ row.documentName }}</span>
<span class="ranking-bar-track">
<span
class="ranking-bar"
:style="{ width: getBarWidth(row), background: getRankColor(index) }"
/>
</span>
<strong class="ranking-count" :style="{ color: getRankColor(index) }">
{{ row.signedCount }}
</strong>
</div>
<div class="ranking-axis" aria-hidden="true">
<span>0</span>
<span>{{ Math.ceil(maxCount / 4) }}</span>
<span>{{ Math.ceil(maxCount / 2) }}</span>
<span>{{ Math.ceil((maxCount * 3) / 4) }}</span>
<span>{{ maxCount }}</span>
</div>
</div>
<div v-else class="ranking-empty">暂无排名数据</div>
</section>
</template>
<style scoped>
.home-panel {
min-width: 0;
padding: 16px 18px;
background: var(--card);
border-radius: var(--r);
box-shadow: var(--sh);
}
.panel-heading {
display: flex;
gap: 10px;
align-items: center;
margin-bottom: 14px;
}
.panel-heading h2 {
margin: 0;
color: var(--ink);
font-size: 14.5px;
line-height: 1.4;
white-space: nowrap;
}
.heading-spacer {
flex: 1;
}
.panel-more {
flex-shrink: 0;
padding: 0;
color: var(--brand);
font-size: 12px;
background: transparent;
}
.panel-more:hover {
color: var(--brand-d);
}
.period-switch {
display: inline-flex;
flex-shrink: 0;
overflow: hidden;
border: 1px solid var(--brand-l);
border-radius: 7px;
}
.period-button {
padding: 5px 12px;
color: var(--mut);
font-size: 11.5px;
background: #fff;
border-right: 1px solid var(--brand-l);
}
.period-button:last-child {
border-right: 0;
}
.period-button:hover:not(.period-button--active) {
color: var(--brand);
background: var(--brand-l);
}
.period-button--active {
color: #fff;
background: var(--brand);
}
.ranking-chart {
display: flex;
min-width: 680px;
flex-direction: column;
gap: 8px;
}
.ranking-row {
display: grid;
grid-template-columns: 24px minmax(170px, 220px) minmax(160px, 1fr) 42px;
gap: 8px;
align-items: center;
min-height: 21px;
}
.ranking-number {
font-size: 11px;
font-weight: 700;
text-align: center;
}
.ranking-name {
overflow: hidden;
color: var(--ink);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.ranking-bar-track {
position: relative;
display: block;
height: 18px;
overflow: hidden;
background-color: #f7fafb;
background-image: linear-gradient(to right, transparent calc(25% - 1px), #eef4f6 25%);
background-size: 25% 100%;
border-radius: 4px;
}
.ranking-bar {
display: block;
height: 100%;
min-width: 2px;
border-radius: 4px;
opacity: 0.82;
}
.ranking-count {
font-size: 12px;
text-align: right;
}
.ranking-axis {
display: flex;
grid-column: 3;
justify-content: space-between;
margin-left: 32px;
padding-top: 1px;
color: var(--mut);
font-size: 10px;
}
.ranking-empty {
display: grid;
min-height: 210px;
color: var(--mut);
font-size: 13px;
place-items: center;
}
@media (max-width: 900px) {
.panel-heading {
align-items: flex-start;
flex-wrap: wrap;
}
.heading-spacer {
display: none;
}
.period-switch {
order: 3;
}
.panel-more {
margin-left: auto;
}
}
</style>