mirror of https://github.com/halo-dev/halo-admin
219 lines
5.2 KiB
Vue
219 lines
5.2 KiB
Vue
<template>
|
|
<VPageHeader title="仪表盘">
|
|
<template #icon>
|
|
<IconDashboard class="mr-2 self-center" />
|
|
</template>
|
|
<template #actions>
|
|
<VSpace>
|
|
<VButton v-if="settings" @click="widgetsModal = true">
|
|
<template #icon>
|
|
<IconAddCircle class="h-full w-full" />
|
|
</template>
|
|
添加组件
|
|
</VButton>
|
|
<VButton type="secondary" @click="settings = !settings">
|
|
<template #icon>
|
|
<IconSettings v-if="!settings" class="h-full w-full" />
|
|
<IconSave v-else class="h-full w-full" />
|
|
</template>
|
|
{{ settings ? "完成" : "设置" }}
|
|
</VButton>
|
|
</VSpace>
|
|
</template>
|
|
</VPageHeader>
|
|
|
|
<div class="dashboard m-4">
|
|
<grid-layout
|
|
v-model:layout="layout"
|
|
:col-num="12"
|
|
:is-draggable="settings"
|
|
:is-resizable="settings"
|
|
:margin="[10, 10]"
|
|
:responsive="true"
|
|
:row-height="30"
|
|
:use-css-transforms="true"
|
|
:vertical-compact="true"
|
|
>
|
|
<grid-item
|
|
v-for="(item, index) in layout"
|
|
:key="index"
|
|
:h="item.h"
|
|
:i="item.i"
|
|
:w="item.w"
|
|
:x="item.x"
|
|
:y="item.y"
|
|
>
|
|
<component :is="item.widget" />
|
|
<div v-if="settings" class="absolute top-2 right-2">
|
|
<IconCloseCircle
|
|
class="cursor-pointer text-lg text-gray-500 hover:text-gray-900"
|
|
@click="handleRemove(item)"
|
|
/>
|
|
</div>
|
|
</grid-item>
|
|
</grid-layout>
|
|
</div>
|
|
|
|
<VModal v-model:visible="widgetsModal" :width="1280" title="小组件">
|
|
<VTabbar
|
|
v-model:active-id="activeId"
|
|
:items="
|
|
widgetsGroup.map((group) => {
|
|
return { id: group.id, label: group.label };
|
|
})
|
|
"
|
|
type="outline"
|
|
></VTabbar>
|
|
|
|
<template v-for="(group, groupIndex) in widgetsGroup" :key="groupIndex">
|
|
<div v-if="activeId === group.id" class="mt-4">
|
|
<VAlert
|
|
v-if="group.notice"
|
|
:description="group.notice"
|
|
class="mb-4"
|
|
title="提示"
|
|
/>
|
|
<grid-layout
|
|
:col-num="12"
|
|
:is-draggable="false"
|
|
:is-resizable="false"
|
|
:layout="group.widgets"
|
|
:margin="[10, 10]"
|
|
:responsive="true"
|
|
:row-height="30"
|
|
:use-css-transforms="true"
|
|
:vertical-compact="true"
|
|
>
|
|
<grid-item
|
|
v-for="(item, index) in group.widgets"
|
|
:key="index"
|
|
:h="item.h"
|
|
:i="item.i"
|
|
:w="item.w"
|
|
:x="item.x"
|
|
:y="item.y"
|
|
class="cursor-pointer"
|
|
@click="handleAddWidget(item)"
|
|
>
|
|
<component :is="item.widget" />
|
|
</grid-item>
|
|
</grid-layout>
|
|
</div>
|
|
</template>
|
|
</VModal>
|
|
</template>
|
|
<script lang="ts" setup>
|
|
import {
|
|
IconAddCircle,
|
|
IconCloseCircle,
|
|
IconDashboard,
|
|
IconSave,
|
|
IconSettings,
|
|
VAlert,
|
|
VButton,
|
|
VModal,
|
|
VPageHeader,
|
|
VSpace,
|
|
VTabbar,
|
|
} from "@halo-dev/components";
|
|
import { ref } from "vue";
|
|
import { useStorage } from "@vueuse/core";
|
|
import cloneDeep from "lodash.clonedeep";
|
|
|
|
const settings = ref(false);
|
|
const widgetsModal = ref(false);
|
|
const activeId = ref("post");
|
|
|
|
const widgetsGroup = [
|
|
{
|
|
id: "post",
|
|
label: "文章",
|
|
widgets: [
|
|
{ x: 0, y: 0, w: 3, h: 3, i: 0, widget: "PostStatsWidget" },
|
|
{ x: 3, y: 0, w: 6, h: 10, i: 1, widget: "RecentPublishedWidget" },
|
|
],
|
|
},
|
|
{
|
|
id: "comment",
|
|
label: "评论",
|
|
widgets: [{ x: 0, y: 0, w: 3, h: 3, i: 0, widget: "CommentStatsWidget" }],
|
|
},
|
|
{
|
|
id: "user",
|
|
label: "用户",
|
|
widgets: [
|
|
{ x: 0, y: 0, w: 3, h: 3, i: 0, widget: "UserStatsWidget" },
|
|
{ x: 3, y: 0, w: 6, h: 10, i: 1, widget: "RecentLoginWidget" },
|
|
],
|
|
},
|
|
{
|
|
id: "plugin-journal",
|
|
label: "日志",
|
|
notice: "此组件由插件 plugin-journal 提供",
|
|
widgets: [{ x: 0, y: 0, w: 5, h: 8, i: 0, widget: "JournalPublishWidget" }],
|
|
},
|
|
{
|
|
id: "other",
|
|
label: "其他",
|
|
widgets: [
|
|
{ x: 0, y: 0, w: 3, h: 3, i: 0, widget: "ViewsStatsWidget" },
|
|
{ x: 0, y: 0, w: 6, h: 10, i: 1, widget: "QuickLinkWidget" },
|
|
],
|
|
},
|
|
];
|
|
|
|
const layout = useStorage("widgets", [
|
|
{ x: 0, y: 0, w: 3, h: 3, i: 0, widget: "PostStatsWidget" },
|
|
{ x: 3, y: 0, w: 3, h: 3, i: 1, widget: "UserStatsWidget" },
|
|
{ x: 6, y: 0, w: 3, h: 3, i: 2, widget: "CommentStatsWidget" },
|
|
{ x: 9, y: 0, w: 3, h: 3, i: 3, widget: "ViewsStatsWidget" },
|
|
{
|
|
x: 4,
|
|
y: 3,
|
|
w: 4,
|
|
h: 10,
|
|
i: 4,
|
|
widget: "RecentPublishedWidget",
|
|
},
|
|
{ x: 0, y: 3, w: 4, h: 10, i: 5, widget: "RecentLoginWidget" },
|
|
{
|
|
x: 8,
|
|
y: 3,
|
|
w: 4,
|
|
h: 8,
|
|
i: 6,
|
|
widget: "JournalPublishWidget",
|
|
},
|
|
]);
|
|
|
|
// eslint-disable-next-line
|
|
function handleAddWidget(widget: any) {
|
|
layout.value = [
|
|
...layout.value,
|
|
{
|
|
...widget,
|
|
i: layout.value.length,
|
|
},
|
|
];
|
|
}
|
|
|
|
// eslint-disable-next-line
|
|
function handleRemove(item: any) {
|
|
const cloneWidgets = cloneDeep(layout.value);
|
|
cloneWidgets.splice(item.i, 1);
|
|
// eslint-disable-next-line
|
|
layout.value = cloneWidgets.map((widget: any, index: number) => {
|
|
return {
|
|
...widget,
|
|
i: index,
|
|
};
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<style>
|
|
.vue-grid-layout {
|
|
@apply -m-[10px];
|
|
}
|
|
</style>
|