151 lines
3.5 KiB
Vue
151 lines
3.5 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
|
|
defineOptions({ name: 'WorkbenchModuleCard' });
|
|
|
|
interface Props {
|
|
title: string;
|
|
icon?: string;
|
|
badgeCount?: number;
|
|
editing?: boolean;
|
|
collapsed?: boolean;
|
|
hasSettings?: boolean;
|
|
}
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
editing: false,
|
|
collapsed: false,
|
|
hasSettings: false
|
|
});
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'toggle-collapse'): void;
|
|
(e: 'hide'): void;
|
|
(e: 'open-settings'): void;
|
|
(e: 'refresh'): void;
|
|
(e: 'navigate'): void;
|
|
}>();
|
|
|
|
const showBody = computed(() => !props.collapsed);
|
|
</script>
|
|
|
|
<template>
|
|
<section class="module-card" :class="{ 'is-editing': editing, 'is-collapsed': collapsed }">
|
|
<header class="module-card__head">
|
|
<span v-if="editing" class="module-drag-handle" title="拖动调整位置">
|
|
<SvgIcon icon="mdi:drag-vertical" />
|
|
</span>
|
|
<SvgIcon v-if="icon" class="module-card__icon" :icon="icon" />
|
|
<span class="module-card__title">{{ title }}</span>
|
|
<span v-if="badgeCount != null" class="module-card__badge">{{ badgeCount }}</span>
|
|
|
|
<div class="module-card__actions">
|
|
<ElButton v-if="editing && hasSettings" link size="small" title="模块设置" @click="emit('open-settings')">
|
|
<SvgIcon icon="mdi:cog-outline" />
|
|
</ElButton>
|
|
<ElButton
|
|
v-if="!editing"
|
|
link
|
|
size="small"
|
|
:title="collapsed ? '展开' : '折叠'"
|
|
@click="emit('toggle-collapse')"
|
|
>
|
|
<SvgIcon :icon="collapsed ? 'mdi:chevron-down' : 'mdi:chevron-up'" />
|
|
</ElButton>
|
|
<ElButton v-if="!editing" link size="small" title="刷新" @click="emit('refresh')">
|
|
<SvgIcon icon="mdi:refresh" />
|
|
</ElButton>
|
|
<ElButton v-if="!editing" link size="small" title="跳详情" @click="emit('navigate')">
|
|
<SvgIcon icon="mdi:open-in-new" />
|
|
</ElButton>
|
|
<ElButton v-if="editing" link size="small" title="隐藏此模块" type="danger" @click="emit('hide')">
|
|
<SvgIcon icon="mdi:close" />
|
|
</ElButton>
|
|
</div>
|
|
</header>
|
|
|
|
<div v-show="showBody" class="module-card__body">
|
|
<slot />
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.module-card {
|
|
background: var(--el-bg-color);
|
|
border: 1px solid var(--el-border-color-lighter);
|
|
border-radius: 10px;
|
|
min-height: 180px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
transition:
|
|
border-color 120ms,
|
|
box-shadow 120ms;
|
|
}
|
|
|
|
.module-card.is-editing {
|
|
border-style: dashed;
|
|
border-color: var(--el-color-primary-light-5);
|
|
}
|
|
|
|
.module-card.is-collapsed {
|
|
min-height: auto;
|
|
}
|
|
|
|
.module-card__head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 14px;
|
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
|
background: var(--el-fill-color-blank);
|
|
}
|
|
|
|
.module-card.is-collapsed .module-card__head {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.module-drag-handle {
|
|
cursor: grab;
|
|
color: var(--el-text-color-secondary);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.module-drag-handle:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.module-card__icon {
|
|
color: var(--el-color-primary);
|
|
font-size: 16px;
|
|
}
|
|
|
|
.module-card__title {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
flex: 1;
|
|
}
|
|
|
|
.module-card__badge {
|
|
background: var(--el-fill-color);
|
|
color: var(--el-text-color-secondary);
|
|
padding: 1px 8px;
|
|
border-radius: 999px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.module-card__actions {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
}
|
|
|
|
.module-card__body {
|
|
flex: 1;
|
|
padding: 14px;
|
|
overflow: auto;
|
|
}
|
|
</style>
|