mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
refactor(charts): share expandable list card
This commit is contained in:
1 parent
bc3a48f877
commit
1b6bf8f3f5
3 files changed
+79
-80
No files matched your search
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import CaptureButton from '@/components/common/CaptureButton.vue'
|
||||
import { SectionCard } from '@/components/UI'
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
description?: string
|
||||
showViewAll: boolean
|
||||
viewAllLabel: string
|
||||
countLabel: string
|
||||
}>()
|
||||
|
||||
const isOpen = ref(false)
|
||||
const modalBodyRef = ref<HTMLElement | null>(null)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SectionCard :title="title" :description="description">
|
||||
<template #headerRight>
|
||||
<div class="no-capture flex items-center gap-2">
|
||||
<slot name="headerRight" />
|
||||
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-3xl' }">
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">
|
||||
{{ viewAllLabel }}
|
||||
</UButton>
|
||||
<template #content>
|
||||
<div ref="modalBodyRef" class="section-content flex flex-col">
|
||||
<div
|
||||
class="flex w-full items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">({{ countLabel }})</span>
|
||||
</div>
|
||||
<CaptureButton size="xs" type="element" :target-element="modalBodyRef" />
|
||||
</div>
|
||||
<div class="max-h-[60vh] overflow-y-auto p-4">
|
||||
<slot name="full" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot />
|
||||
</SectionCard>
|
||||
</template>
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import CaptureButton from '@/components/common/CaptureButton.vue'
|
||||
import { SectionCard } from '@/components/UI'
|
||||
import ExpandableListCard from './ExpandableListCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -24,12 +23,6 @@ const props = withDefaults(
|
||||
}
|
||||
)
|
||||
|
||||
// 控制弹窗
|
||||
const isOpen = ref(false)
|
||||
|
||||
// 截屏相关 ref
|
||||
const modalBodyRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// Top N 数据
|
||||
const topNData = computed(() => props.items.slice(0, props.topN))
|
||||
|
||||
@@ -44,38 +37,22 @@ const formattedCount = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SectionCard :title="title" :description="description">
|
||||
<ExpandableListCard
|
||||
:title="title"
|
||||
:description="description"
|
||||
:show-view-all="showViewAll"
|
||||
:view-all-label="t('views.charts.listPro.fullRanking')"
|
||||
:count-label="formattedCount"
|
||||
>
|
||||
<template #headerRight>
|
||||
<div class="no-capture flex items-center gap-2">
|
||||
<!-- 自定义头部右侧内容 -->
|
||||
<slot name="headerRight" />
|
||||
<slot name="headerRight" />
|
||||
</template>
|
||||
|
||||
<!-- 完整列表弹窗 -->
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-3xl' }">
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">
|
||||
{{ t('views.charts.listPro.fullRanking') }}
|
||||
</UButton>
|
||||
<template #content>
|
||||
<div ref="modalBodyRef" class="section-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex w-full items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">({{ formattedCount }})</span>
|
||||
</div>
|
||||
<CaptureButton size="xs" type="element" :target-element="modalBodyRef" />
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="max-h-[60vh] p-4 divide-y divide-gray-100 overflow-y-auto dark:divide-gray-800">
|
||||
<div v-for="(item, index) in items" :key="index" class="px-5 py-3">
|
||||
<slot name="item" :item="item" :index="index" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
<template #full>
|
||||
<div class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<div v-for="(item, index) in items" :key="index" class="px-5 py-3">
|
||||
<slot name="item" :item="item" :index="index" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -95,5 +72,5 @@ const formattedCount = computed(() => {
|
||||
<div class="px-5 py-8 text-center text-sm text-gray-400">{{ t('views.charts.listPro.empty') }}</div>
|
||||
</slot>
|
||||
</div>
|
||||
</SectionCard>
|
||||
</ExpandableListCard>
|
||||
</template>
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import RankList from './RankList.vue'
|
||||
import type { RankItem } from './RankList.vue'
|
||||
import CaptureButton from '@/components/common/CaptureButton.vue'
|
||||
import { SectionCard } from '@/components/UI'
|
||||
import ExpandableListCard from './ExpandableListCard.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@@ -25,12 +24,6 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
topN: 10,
|
||||
})
|
||||
|
||||
// 控制弹窗
|
||||
const isOpen = ref(false)
|
||||
|
||||
// 截屏相关 ref
|
||||
const modalBodyRef = ref<HTMLElement | null>(null)
|
||||
|
||||
// Top N 数据
|
||||
const topNData = computed(() => {
|
||||
return props.members.slice(0, props.topN)
|
||||
@@ -43,38 +36,17 @@ const showViewAll = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SectionCard :title="title" :description="description">
|
||||
<template #headerRight>
|
||||
<div class="no-capture flex items-center gap-1">
|
||||
<!-- 完整排行榜 Dialog -->
|
||||
<UModal v-model:open="isOpen" :ui="{ content: 'md:w-full max-w-3xl' }">
|
||||
<UButton v-if="showViewAll" icon="i-heroicons-list-bullet" variant="ghost">
|
||||
{{ t('views.charts.rankListPro.fullRanking') }}
|
||||
</UButton>
|
||||
<template #content>
|
||||
<div ref="modalBodyRef" class="section-content flex flex-col">
|
||||
<!-- Header -->
|
||||
<div
|
||||
class="flex w-full items-center justify-between border-b border-gray-200 px-6 py-4 dark:border-gray-700"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 whitespace-nowrap dark:text-white">{{ title }}</h3>
|
||||
<span class="text-sm text-gray-500">
|
||||
({{ t('views.charts.rankListPro.memberCount', { count: members.length }) }})
|
||||
</span>
|
||||
</div>
|
||||
<CaptureButton size="xs" type="element" :target-element="modalBodyRef" />
|
||||
</div>
|
||||
<!-- Body -->
|
||||
<div class="max-h-[60vh] p-4 overflow-y-auto">
|
||||
<RankList :members="members" :unit="unit" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</div>
|
||||
<ExpandableListCard
|
||||
:title="title"
|
||||
:description="description"
|
||||
:show-view-all="showViewAll"
|
||||
:view-all-label="t('views.charts.rankListPro.fullRanking')"
|
||||
:count-label="t('views.charts.rankListPro.memberCount', { count: members.length })"
|
||||
>
|
||||
<template #full>
|
||||
<RankList :members="members" :unit="unit" />
|
||||
</template>
|
||||
|
||||
<RankList :members="topNData" :unit="unit" />
|
||||
</SectionCard>
|
||||
</ExpandableListCard>
|
||||
</template>
|
||||
Reference in new issue
Block a user