33 lines
2.5 KiB
Vue
33 lines
2.5 KiB
Vue
<script setup lang="ts">
|
||||
|
|
import { computed, ref } from 'vue'
|
|||
|
|
import type { Quantity } from '../types'
|
|||
|
|
import { quantityText } from '../state'
|
|||
|
|
export interface BarRow { key: string; label: string; secondary?: string; value: Quantity; detail: string }
|
|||
|
|
const props = defineProps<{ rows: BarRow[]; unit: string; disabled?: boolean; title: string }>()
|
|||
|
|
const emit = defineEmits<{ select: [key: string] }>()
|
|||
|
|
const focused = ref('')
|
|||
|
|
const maximum = computed(() => Math.max(1, ...props.rows.map(row => Number(row.value ?? 0))))
|
|||
|
|
</script>
|
|||
|
|
<template>
|
|||
|
|
<div :aria-label="title" class="dashboard-bars">
|
|||
|
|
<p v-if="!rows.length">暂无符合条件的 SKU;请核对覆盖与待评估资料。</p>
|
|||
|
|
<button v-for="row in rows" :key="row.key" type="button" class="bar-row" :aria-disabled="disabled || row.value == null" :aria-label="`${row.label},${row.secondary ?? ''},${quantityText(row.value)} ${unit};${row.detail}`" :title="`${row.label} ${row.secondary ?? ''};${row.detail}`" @focus="focused = row.key" @mouseenter="focused = row.key" @click="!disabled && row.value != null && emit('select', row.key)">
|
|||
|
|
<span class="bar-heading"><span class="bar-label">{{ row.label }}</span><strong>{{ quantityText(row.value) }} {{ unit }}</strong></span>
|
|||
|
|
<span v-if="row.secondary" class="bar-secondary">{{ row.secondary }}</span>
|
|||
|
|
<svg viewBox="0 0 500 15" aria-hidden="true"><line x1="0" x2="500" y1="14" y2="14" stroke="var(--el-border-color)" /><rect v-if="row.value != null" x="0" y="1" :width="Number(row.value) / maximum * 500" height="11" rx="2" fill="var(--el-color-primary)" /></svg>
|
|||
|
|
</button>
|
|||
|
|
<p v-if="focused" class="bar-detail" role="status">{{ rows.find(row => row.key === focused)?.detail }}</p>
|
|||
|
|
</div>
|
|||
|
|
</template>
|
|||
|
|
<style scoped>
|
|||
|
|
.dashboard-bars { display: grid; gap: var(--admin-space-2); }
|
|||
|
|
.bar-row { appearance: none; border: 0; padding: var(--admin-space-2) 0; background: transparent; text-align: left; color: inherit; cursor: pointer; width: 100%; font: inherit; }
|
|||
|
|
.bar-row:focus-visible { outline: 2px solid var(--el-color-primary); outline-offset: 2px; }
|
|||
|
|
.bar-row[aria-disabled="true"] { cursor: default; }
|
|||
|
|
.bar-heading { display: flex; gap: var(--admin-space-2); justify-content: space-between; align-items: baseline; }
|
|||
|
|
.bar-label { overflow-wrap: anywhere; }
|
|||
|
|
.bar-heading strong { white-space: nowrap; }
|
|||
|
|
.bar-secondary, .bar-detail { display: block; color: var(--el-text-color-secondary); font-size: var(--admin-font-caption); overflow-wrap: anywhere; }
|
|||
|
|
svg { display: block; width: 100%; height: 15px; }
|
|||
|
|
</style>
|