mirror of
https://github.com/vbenjs/vue-vben-admin.git
synced 2025-08-27 15:59:33 +08:00
feat: add dashboard page
This commit is contained in:
@@ -46,9 +46,10 @@
|
||||
"@vben-core/shadcn-ui": "workspace:*",
|
||||
"@vben/chart-ui": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vueuse/integrations": "^10.11.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"vue": "^3.4.30",
|
||||
"vue": "^3.4.31",
|
||||
"vue-router": "^4.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@@ -13,9 +13,9 @@ defineOptions({
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
description:
|
||||
'是一个基于Vue3.0、Vite 、TypeScript 等前沿技术的后台解决方案,目标是为服务中大型项目开发,提供现成的开箱解决方案及丰富的示例。',
|
||||
'是一个现代化开箱即用的中后台解决方案,采用最新的技术栈,包括 Vue 3.0、Vite、TailwindCSS 和 TypeScript 等前沿技术,代码规范严谨,提供丰富的配置选项,旨在为中大型项目的开发提供现成的开箱即用解决方案及丰富的示例,同时,它也是学习和深入前端技术的一个极佳示例。',
|
||||
name: 'Vben Admin Pro',
|
||||
title: '关于我们',
|
||||
title: '关于项目',
|
||||
});
|
||||
|
||||
const {
|
||||
@@ -29,7 +29,9 @@ const {
|
||||
license,
|
||||
repositoryUrl,
|
||||
version,
|
||||
} = window.__VBEN_ADMIN_METADATA__ || {};
|
||||
// vite inject-metadata 插件注入的全局变量
|
||||
// eslint-disable-next-line no-undef
|
||||
} = __VBEN_ADMIN_METADATA__ || {};
|
||||
|
||||
const vbenDescriptionItems: DescriptionItem[] = [
|
||||
{
|
||||
@@ -105,7 +107,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
|
||||
|
||||
<template>
|
||||
<div class="m-5">
|
||||
<div class="bg-card rounded-md p-5">
|
||||
<div class="bg-card border-border rounded-md border p-5 shadow">
|
||||
<div>
|
||||
<h3 class="text-foreground text-2xl font-semibold leading-7">
|
||||
{{ title }}
|
||||
@@ -133,7 +135,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card mt-6 rounded-md p-5">
|
||||
<div class="bg-card border-border mt-6 rounded-md border p-5">
|
||||
<div>
|
||||
<h5 class="text-foreground text-lg">生产环境依赖</h5>
|
||||
</div>
|
||||
@@ -152,8 +154,7 @@ const devDependenciesItems = Object.keys(devDependencies).map((key) => ({
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-card mt-6 rounded-md p-5">
|
||||
<div class="bg-card border-border mt-6 rounded-md border p-5">
|
||||
<div>
|
||||
<h5 class="text-foreground text-lg">开发环境依赖</h5>
|
||||
</div>
|
||||
|
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisChartCard',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-xl">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<slot></slot>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
import type { TabsItem } from '@vben/types';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
tabs: TabsItem[];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisChartsTabs',
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
tabs: () => [],
|
||||
});
|
||||
|
||||
const defaultValue = computed(() => {
|
||||
return props.tabs?.[0].value;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="bg-card border-border w-full rounded-xl border px-4 pb-5 pt-3 shadow"
|
||||
>
|
||||
<Tabs :default-value="defaultValue">
|
||||
<TabsList>
|
||||
<template v-for="tab in tabs" :key="tab.label">
|
||||
<TabsTrigger :value="tab.value"> {{ tab.label }} </TabsTrigger>
|
||||
</template>
|
||||
</TabsList>
|
||||
<template v-for="tab in tabs" :key="tab.label">
|
||||
<TabsContent :value="tab.value" class="pt-4">
|
||||
<slot :name="tab.value"></slot>
|
||||
</TabsContent>
|
||||
</template>
|
||||
</Tabs>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,59 @@
|
||||
<script setup lang="ts">
|
||||
import type { AnalysisOverviewItem } from '../typing';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
VbenCountToAnimator,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items: AnalysisOverviewItem[];
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'AnalysisOverview',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="md:flex">
|
||||
<template v-for="(item, index) in items" :key="item.title">
|
||||
<Card
|
||||
:class="{ 'md:mr-4': index + 1 < 4 }"
|
||||
:title="item.title"
|
||||
class="mt-5 w-full md:mt-0 md:w-1/4"
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle class="text-xl">{{ item.title }}</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="flex items-center justify-between">
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.value"
|
||||
:start-val="1"
|
||||
class="text-xl"
|
||||
prefix=""
|
||||
/>
|
||||
<VbenIcon :icon="item.icon" class="size-8 flex-shrink-0" />
|
||||
</CardContent>
|
||||
<CardFooter class="justify-between">
|
||||
<span>{{ item.totalTitle }}</span>
|
||||
<VbenCountToAnimator
|
||||
:end-val="item.totalValue"
|
||||
:start-val="1"
|
||||
prefix=""
|
||||
/>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,3 @@
|
||||
export { default as AnalysisChartCard } from './analysis-chart-card.vue';
|
||||
export { default as AnalysisChartsTabs } from './analysis-charts-tabs.vue';
|
||||
export { default as AnalysisOverview } from './analysis-overview.vue';
|
@@ -1,45 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { VbenIcon, Badge } from '@vben-core/shadcn-ui';
|
||||
defineOptions({ name: 'DashboardCard' });
|
||||
import type { CardItem } from './typings';
|
||||
interface Props {
|
||||
item: CardItem;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border-2 border-solid">
|
||||
<div class="flex justify-between p-2">
|
||||
<div class="">
|
||||
<slot name="title">{{ item.title }}</slot>
|
||||
</div>
|
||||
<div class="text-xs" :class="`bg-${item.color}-500`">
|
||||
<slot name="extra"
|
||||
><Badge>{{ item.extra }}</Badge></slot
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-2 mr-2">
|
||||
<div class="m-2 flex justify-between">
|
||||
<div class="text-4xl">
|
||||
<slot name="leftContent">{{ item.leftContent }}</slot>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="rightContent"
|
||||
><VbenIcon :icon="item.rightContent" class="size-10"
|
||||
/></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-2 flex justify-between">
|
||||
<div>
|
||||
<slot name="leftFooter">{{ item.leftFooter }}</slot>
|
||||
</div>
|
||||
<div>
|
||||
<slot name="rightFooter">{{ item.rightFooter }}</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -1,24 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { chart } from '@vben/chart-ui';
|
||||
defineOptions({ name: 'ChartCard' });
|
||||
import type { ChartItem } from './typings';
|
||||
import { onMounted, ref } from 'vue';
|
||||
interface Props {
|
||||
item: ChartItem;
|
||||
}
|
||||
const chartRef = ref();
|
||||
onMounted(() => {
|
||||
chartRef.value.setChart(props.item.option);
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border-2 border-solid">
|
||||
<div class="">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<chart ref="chartRef" />
|
||||
</div>
|
||||
</template>
|
@@ -1,41 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import { Tabs, TabsList, TabsTrigger } from '@vben-core/shadcn-ui';
|
||||
import { chart } from '@vben/chart-ui';
|
||||
defineOptions({ name: 'ChartTab' });
|
||||
import type { ChartItem } from './typings';
|
||||
import { onMounted, ref } from 'vue';
|
||||
interface Props {
|
||||
items: ChartItem[];
|
||||
}
|
||||
const chartRef = ref();
|
||||
onMounted(() => {
|
||||
change(0);
|
||||
});
|
||||
const change = (i) => {
|
||||
const item = props.items[i];
|
||||
if (!item) return;
|
||||
item.option && chartRef.value.setChart(item.option);
|
||||
};
|
||||
const props = withDefaults(defineProps<Props>(), {});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="rounded-lg border-2 border-solid">
|
||||
<Tabs
|
||||
:defaultValue="items[0].name"
|
||||
className="w-[400px]"
|
||||
@update:modelValue="change"
|
||||
>
|
||||
<TabsList className="flex w-full ">
|
||||
<TabsTrigger
|
||||
:value="index"
|
||||
v-for="(item, index) in items"
|
||||
:key="index"
|
||||
>{{ item.title }}</TabsTrigger
|
||||
>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
|
||||
<chart ref="chartRef" />
|
||||
</div>
|
||||
</template>
|
@@ -1,156 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CardItem, ChartItem } from './typings';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import Card from './card.vue';
|
||||
import ChartCard from './chartCard.vue';
|
||||
import ChartTab from './chartTab.vue';
|
||||
|
||||
interface Props {
|
||||
cardList: CardItem[];
|
||||
chartTabs: ChartItem[];
|
||||
}
|
||||
|
||||
defineOptions({ name: 'Dashboard' });
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
cardList: () => [],
|
||||
chartTabs: () => [],
|
||||
});
|
||||
|
||||
const itemA = ref({
|
||||
option: {
|
||||
legend: {
|
||||
top: 'bottom',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
center: ['50%', '50%'],
|
||||
data: [
|
||||
{ name: 'rose 1', value: 40 },
|
||||
{ name: 'rose 2', value: 38 },
|
||||
{ name: 'rose 3', value: 32 },
|
||||
{ name: 'rose 4', value: 30 },
|
||||
{ name: 'rose 5', value: 28 },
|
||||
{ name: 'rose 6', value: 26 },
|
||||
{ name: 'rose 7', value: 22 },
|
||||
{ name: 'rose 8', value: 18 },
|
||||
],
|
||||
itemStyle: {
|
||||
borderRadius: 8,
|
||||
},
|
||||
name: 'Nightingale Chart',
|
||||
radius: [50, 200],
|
||||
roseType: 'area',
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
toolbox: {
|
||||
feature: {
|
||||
dataView: { readOnly: false, show: true },
|
||||
mark: { show: true },
|
||||
restore: { show: true },
|
||||
saveAsImage: { show: true },
|
||||
},
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
title: '玫瑰图',
|
||||
});
|
||||
const itemB = ref({
|
||||
option: {
|
||||
legend: {
|
||||
data: ['Allocated Budget', 'Actual Spending'],
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ max: 6500, name: 'Sales' },
|
||||
{ max: 16_000, name: 'Administration' },
|
||||
{ max: 30_000, name: 'Information Technology' },
|
||||
{ max: 38_000, name: 'Customer Support' },
|
||||
{ max: 52_000, name: 'Development' },
|
||||
{ max: 25_000, name: 'Marketing' },
|
||||
],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [
|
||||
{
|
||||
name: 'Allocated Budget',
|
||||
value: [4200, 3000, 20_000, 35_000, 50_000, 18_000],
|
||||
},
|
||||
{
|
||||
name: 'Actual Spending',
|
||||
value: [5000, 14_000, 28_000, 26_000, 42_000, 21_000],
|
||||
},
|
||||
],
|
||||
name: 'Budget vs spending',
|
||||
type: 'radar',
|
||||
},
|
||||
],
|
||||
},
|
||||
title: '雷达图',
|
||||
});
|
||||
const itemC = ref({
|
||||
option: {
|
||||
legend: {
|
||||
left: 'center',
|
||||
top: '5%',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
avoidLabelOverlap: false,
|
||||
data: [
|
||||
{ name: 'Search Engine', value: 1048 },
|
||||
{ name: 'Direct', value: 735 },
|
||||
{ name: 'Email', value: 580 },
|
||||
{ name: 'Union Ads', value: 484 },
|
||||
{ name: 'Video Ads', value: 300 },
|
||||
],
|
||||
emphasis: {
|
||||
label: {
|
||||
fontSize: 40,
|
||||
fontWeight: 'bold',
|
||||
show: true,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: '#fff',
|
||||
borderRadius: 10,
|
||||
borderWidth: 2,
|
||||
},
|
||||
label: {
|
||||
position: 'center',
|
||||
show: false,
|
||||
},
|
||||
labelLine: {
|
||||
show: false,
|
||||
},
|
||||
name: 'Access From',
|
||||
radius: ['40%', '70%'],
|
||||
type: 'pie',
|
||||
},
|
||||
],
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
},
|
||||
title: '饼图',
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="grid grid-cols-4 gap-4 p-2">
|
||||
<Card v-for="item in cardList" :key="item.title" :item="item" />
|
||||
</div>
|
||||
<div class="p-2"><ChartTab :items="chartTabs" /></div>
|
||||
<div class="grid grid-cols-3 gap-2 p-2">
|
||||
<ChartCard :item="itemA" />
|
||||
<ChartCard :item="itemB" />
|
||||
<ChartCard :item="itemC" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -1,3 +1,3 @@
|
||||
export { default as DashboardLayout } from './layout.vue';
|
||||
export { default as Dashboard } from './dashboard.vue';
|
||||
export { default as chartCard } from './chartCard.vue';
|
||||
export * from './analysis';
|
||||
export type * from './typing';
|
||||
export * from './workbench';
|
||||
|
@@ -1,7 +0,0 @@
|
||||
<script lang="ts" setup>
|
||||
defineOptions({ name: 'DashboardLayout' });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>dashboardLayout</div>
|
||||
</template>
|
29
packages/business/universal-ui/src/dashboard/typing.ts
Normal file
29
packages/business/universal-ui/src/dashboard/typing.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { Component } from 'vue';
|
||||
|
||||
interface AnalysisOverviewItem {
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
totalTitle: string;
|
||||
totalValue: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface WorkbenchProjectItem {
|
||||
color?: string;
|
||||
content: string;
|
||||
date: string;
|
||||
group: string;
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
}
|
||||
interface WorkbenchQuickNavItem {
|
||||
color?: string;
|
||||
icon: Component | string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export type {
|
||||
AnalysisOverviewItem,
|
||||
WorkbenchProjectItem,
|
||||
WorkbenchQuickNavItem,
|
||||
};
|
@@ -1,17 +0,0 @@
|
||||
interface CardItem {
|
||||
title: string;
|
||||
extra: string;
|
||||
leftContent: string;
|
||||
rightContent: string;
|
||||
color?: string;
|
||||
leftFooter: string;
|
||||
rightFooter: string;
|
||||
}
|
||||
|
||||
interface ChartItem {
|
||||
name: string;
|
||||
title: string;
|
||||
options: any;
|
||||
}
|
||||
|
||||
export type { CardItem, ChartItem };
|
@@ -0,0 +1,3 @@
|
||||
export { default as WorkbenchHeader } from './workbench-header.vue';
|
||||
export { default as WorkbenchProject } from './workbench-project.vue';
|
||||
export { default as WorkbenchQuickNav } from './workbench-quick-nav.vue';
|
@@ -0,0 +1,46 @@
|
||||
<script lang="ts" setup>
|
||||
import { VbenAvatar } from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'WorkbenchHeader',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
avatar: '',
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-card border-border rounded-xl p-4 py-6 shadow lg:flex">
|
||||
<VbenAvatar :src="avatar" class="size-20" />
|
||||
<div
|
||||
v-if="$slots.title || $slots.description"
|
||||
class="flex flex-col justify-center md:ml-6 md:mt-0"
|
||||
>
|
||||
<h1 v-if="$slots.title" class="text-md font-semibold md:text-xl">
|
||||
<slot name="title"></slot>
|
||||
</h1>
|
||||
<span v-if="$slots.description" class="text-foreground/80 mt-1">
|
||||
<slot name="description"></slot>
|
||||
</span>
|
||||
</div>
|
||||
<div class="mt-4 flex flex-1 justify-end md:mt-0">
|
||||
<div class="flex flex-col justify-center text-right">
|
||||
<span class="text-foreground/80"> 待办 </span>
|
||||
<span class="text-2xl">2/10</span>
|
||||
</div>
|
||||
|
||||
<div class="mx-12 flex flex-col justify-center text-right md:mx-16">
|
||||
<span class="text-foreground/80"> 项目 </span>
|
||||
<span class="text-2xl">8</span>
|
||||
</div>
|
||||
<div class="mr-4 flex flex-col justify-center text-right md:mr-10">
|
||||
<span class="text-foreground/80"> 团队 </span>
|
||||
<span class="text-2xl">300</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@@ -0,0 +1,56 @@
|
||||
<script setup lang="ts">
|
||||
import type { WorkbenchProjectItem } from '../typing';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items: WorkbenchProjectItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'WorkbenchProject',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader class="py-4">
|
||||
<CardTitle class="text-lg">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap p-0">
|
||||
<template v-for="(item, index) in items" :key="item.title">
|
||||
<div
|
||||
:class="{
|
||||
'border-r-0': index % 3 === 2,
|
||||
'border-b-0': index < 3,
|
||||
'pb-4': index > 2,
|
||||
}"
|
||||
class="border-border w-1/3 border-b border-r border-t p-4 transition-all hover:shadow-xl"
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<VbenIcon :color="item.color" :icon="item.icon" class="size-8" />
|
||||
<span class="ml-4 text-lg font-medium">{{ item.title }}</span>
|
||||
</div>
|
||||
<div class="text-foreground/80 mt-4 flex h-10">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<div class="text-foreground/80 flex justify-between">
|
||||
<span>{{ item.group }}</span>
|
||||
<span>{{ item.date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import type { WorkbenchQuickNavItem } from '../typing';
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
VbenIcon,
|
||||
} from '@vben-core/shadcn-ui';
|
||||
|
||||
interface Props {
|
||||
items: WorkbenchQuickNavItem[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
defineOptions({
|
||||
name: 'WorkbenchQuickNav',
|
||||
});
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
items: () => [],
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<CardHeader class="py-4">
|
||||
<CardTitle class="text-lg">{{ title }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="flex flex-wrap p-0">
|
||||
<template v-for="(item, index) in items" :key="item.title">
|
||||
<div
|
||||
:class="{
|
||||
'border-r-0': index % 3 === 2,
|
||||
'pb-4': index > 2,
|
||||
'border-b-0': index < 3,
|
||||
}"
|
||||
class="flex-col-center border-border w-1/3 border-b border-r border-t py-5 transition-all hover:shadow-xl"
|
||||
>
|
||||
<VbenIcon :color="item.color" :icon="item.icon" class="size-5" />
|
||||
<span class="text-md mt-2 truncate">{{ item.title }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@vben/tsconfig/web.json",
|
||||
"compilerOptions": {
|
||||
"types": ["@vben/types/window"]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user