113 lines
4.5 KiB
Vue
113 lines
4.5 KiB
Vue
<script setup>
|
|
import { NEl, NFlex, NIcon, NText } from 'naive-ui'
|
|
import { TbChevronRight } from 'vue-icons-plus/tb'
|
|
import { useThemeVars } from 'naive-ui'
|
|
import { computed } from 'vue'
|
|
|
|
const props = defineProps({
|
|
title: { type: String, default: '' },
|
|
// null / имя цвета темы (primary|success|error|warning|info) / 'default' = нейтральный тёмный
|
|
color: { type: String, default: null },
|
|
// [{ label, href? }]
|
|
breadcrumbs: { type: Array, default: () => [] },
|
|
// компонент-иконка (если не нужен кастомный слот #icon)
|
|
icon: { type: [Object, Function], default: null },
|
|
})
|
|
|
|
const themeVars = useThemeVars()
|
|
|
|
const isNeutral = computed(() => props.color === 'default')
|
|
const cssColor = computed(() => isNeutral.value ? null : `var(--${props.color ?? 'primary'}-color)`)
|
|
|
|
const bannerBg = computed(() => isNeutral.value
|
|
? themeVars.value.cardColor
|
|
: `color-mix(in srgb, ${cssColor.value} 8%, ${themeVars.value.cardColor})`
|
|
)
|
|
const bannerBorder = computed(() => isNeutral.value
|
|
? themeVars.value.dividerColor
|
|
: `color-mix(in srgb, ${cssColor.value} 20%, transparent)`
|
|
)
|
|
const iconBg = computed(() => isNeutral.value
|
|
? `color-mix(in srgb, ${themeVars.value.textColor1} 10%, transparent)`
|
|
: `color-mix(in srgb, ${cssColor.value} 16%, transparent)`
|
|
)
|
|
const iconColor = computed(() => isNeutral.value
|
|
? themeVars.value.textColor2
|
|
: cssColor.value
|
|
)
|
|
const crumbColor = computed(() => isNeutral.value
|
|
? themeVars.value.primaryColor
|
|
: cssColor.value
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<NEl
|
|
class="rounded-2xl px-6 py-5"
|
|
:style="`background: ${bannerBg}; border: 1px solid ${bannerBorder};`"
|
|
>
|
|
<!-- Хлебные крошки -->
|
|
<NFlex v-if="breadcrumbs.length" align="center" :size="6" style="margin-bottom: 12px;">
|
|
<template v-for="(crumb, i) in breadcrumbs" :key="i">
|
|
<NEl
|
|
v-if="crumb.href"
|
|
:tag="crumb.tag ?? 'a'"
|
|
:href="crumb.href"
|
|
:style="`font-size: 12px; color: ${crumbColor}; text-decoration: none; opacity: .75; transition: opacity .15s;`"
|
|
@mouseenter="e => e.currentTarget.style.opacity = 1"
|
|
@mouseleave="e => e.currentTarget.style.opacity = .75"
|
|
>
|
|
<NFlex align="center" :size="4">
|
|
<NIcon v-if="crumb.icon" size="13"><component :is="crumb.icon" /></NIcon>
|
|
{{ crumb.label }}
|
|
</NFlex>
|
|
</NEl>
|
|
<NText v-else depth="3" style="font-size: 12px;">{{ crumb.label }}</NText>
|
|
<NIcon v-if="i < breadcrumbs.length - 1" size="12" depth="3"><TbChevronRight /></NIcon>
|
|
</template>
|
|
</NFlex>
|
|
|
|
<!-- Основная строка -->
|
|
<NFlex align="center" justify="space-between" :wrap="false">
|
|
|
|
<NFlex align="center" :size="14" :wrap="false">
|
|
<!-- Иконка: кастомный слот или prop -->
|
|
<NEl
|
|
v-if="$slots.icon || icon"
|
|
class="rounded-xl"
|
|
:style="`
|
|
width: 48px; height: 48px; flex-shrink: 0;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: ${iconBg};
|
|
`"
|
|
>
|
|
<slot name="icon">
|
|
<NIcon :size="24" :style="`color: ${iconColor};`">
|
|
<component :is="icon" />
|
|
</NIcon>
|
|
</slot>
|
|
</NEl>
|
|
|
|
<div class="min-w-0">
|
|
<!-- Заголовок: кастомный слот или prop -->
|
|
<slot name="title">
|
|
<span style="font-size: 20px; font-weight: 700; line-height: 1.2;">
|
|
{{ title }}
|
|
</span>
|
|
</slot>
|
|
<!-- Мета-строка -->
|
|
<div v-if="$slots.meta" style="margin-top: 5px;">
|
|
<slot name="meta" />
|
|
</div>
|
|
</div>
|
|
</NFlex>
|
|
|
|
<!-- Действия (кнопки) -->
|
|
<NFlex v-if="$slots.actions" :size="8" style="flex-shrink: 0;">
|
|
<slot name="actions" />
|
|
</NFlex>
|
|
|
|
</NFlex>
|
|
</NEl>
|
|
</template>
|