Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 140 additions & 66 deletions dashboard/src/components/shared/ItemCard.vue
Original file line number Diff line number Diff line change
@@ -1,71 +1,93 @@
<template>
<v-card class="item-card hover-elevation" style="padding: 4px;" elevation="0">
<v-card-title class="d-flex justify-space-between align-center pb-1 pt-3">
<span class="text-h2 text-truncate" :title="getItemTitle()">{{ getItemTitle() }}</span>
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-switch
<v-card class="item-card hover-elevation pa-4" elevation="0">
<div class="item-card-content" :style="{ opacity: getItemEnabled() ? 1 : 0.6 }">
<!-- 标题 + 开关 -->
<div class="item-card-header">
<div class="item-card-title text-h4 font-weight-bold" :title="getItemTitle()">
{{ getItemTitle() }}
</div>

<div class="item-card-switch">
<v-tooltip location="top">
<template v-slot:activator="{ props }">
<v-switch
color="primary"
hide-details
density="compact"
:model-value="getItemEnabled()"
:loading="loading"
:disabled="loading"
v-bind="props"
@update:model-value="toggleEnabled"
inset
></v-switch>
</template>
<span>{{ getItemEnabled() ? t('core.common.itemCard.enabled') : t('core.common.itemCard.disabled') }}</span>
</v-tooltip>
</div>
</div>

<!-- 中间层 UI -->
<div class="item-card-details">
<slot name="item-details" :item="item"></slot>
</div>

<!-- logo + 操作按钮 -->
<div class="item-card-footer">
<div v-if="bglogo" class="item-card-logo">
<v-img
:src="bglogo"
width="64"
height="64"
contain
:style="{ opacity: getItemEnabled() ? 0.9 : 0.4 }"
></v-img>
</div>

<div class="item-card-actions">
<v-btn
variant="outlined"
color="primary"
hide-details
density="compact"
:model-value="getItemEnabled()"
:loading="loading"
rounded="pill"
size="small"
block
:disabled="loading"
v-bind="props"
@update:model-value="toggleEnabled"
></v-switch>
</template>
<span>{{ getItemEnabled() ? t('core.common.itemCard.enabled') : t('core.common.itemCard.disabled') }}</span>
</v-tooltip>
</v-card-title>

<v-card-text>
<slot name="item-details" :item="item"></slot>
</v-card-text>

<v-card-actions style="margin: 8px;">
<v-btn
variant="outlined"
color="error"
size="small"
rounded="xl"
:disabled="loading"
@click="$emit('delete', item)"
>
{{ t('core.common.itemCard.delete') }}
</v-btn>
<v-btn
variant="tonal"
color="primary"
size="small"
rounded="xl"
:disabled="loading"
@click="$emit('edit', item)"
>
{{ t('core.common.itemCard.edit') }}
</v-btn>
<v-btn
v-if="showCopyButton"
variant="tonal"
color="secondary"
size="small"
rounded="xl"
:disabled="loading"
@click="$emit('copy', item)"
>
{{ t('core.common.itemCard.copy') }}
</v-btn>
<slot name="actions" :item="item"></slot>
<v-spacer></v-spacer>
</v-card-actions>

<div class="d-flex justify-end align-center" style="position: absolute; bottom: 16px; right: 16px; opacity: 0.2;" v-if="bglogo">
<v-img
:src="bglogo"
contain
width="120"
height="120"
></v-img>
@click="$emit('edit', item)"
>
<v-icon start>mdi-pencil</v-icon>
{{ t('core.common.itemCard.edit') }}
</v-btn>

<v-btn
variant="outlined"
color="error"
rounded="pill"
size="small"
block
:disabled="loading"
@click="$emit('delete', item)"
>
<v-icon start>mdi-delete-outline</v-icon>
{{ t('core.common.itemCard.delete') }}
</v-btn>

<v-btn
v-if="showCopyButton"
variant="tonal"
color="secondary"
rounded="pill"
size="small"
block
:disabled="loading"
@click="$emit('copy', item)"
>
<v-icon start>mdi-content-copy</v-icon>
{{ t('core.common.itemCard.copy') }}
</v-btn>

<slot name="actions" :item="item"></slot>
</div>
</div>
</div>
</v-card>
</template>
Expand Down Expand Up @@ -126,10 +148,62 @@ export default {
border-radius: 18px;
transition: all 0.3s ease;
overflow: hidden;
min-height: 220px;
min-height: 160px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
border: 1px solid #e0e0e0;
}

.item-card-content {
position: relative;
overflow: hidden;
flex: 1 1 auto;
height: 100%;
display: flex;
flex-direction: column;
}

.item-card-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
}

.item-card-title {
min-width: 0;
flex: 1 1 auto;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.item-card-switch {
flex: 0 0 auto;
}

.item-card-details {
width: 100%;
margin-top: 6px;
margin-bottom: 10px;
}

.item-card-footer {
margin-top: auto;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 12px;
}

.item-card-actions {
min-width: 100px;
display: flex;
flex-direction: column;
gap: 8px;
align-items: stretch;
}

.hover-elevation:hover {
Expand Down
121 changes: 57 additions & 64 deletions dashboard/src/views/PlatformPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,63 +16,60 @@
</v-btn>
</v-row>

<div>
<div class="px-4">
<v-row v-if="(config_data.platform || []).length === 0">
<v-col cols="12" class="text-center pa-8">
<v-icon size="64" color="grey-lighten-1">mdi-connection</v-icon>
<p class="text-grey mt-4">{{ tm('emptyText') }}</p>
</v-col>
</v-row>

<v-row v-else>
<v-col v-for="(platform, index) in config_data.platform || []" :key="index" cols="12" md="6" lg="4" xl="3">
<item-card :item="platform" title-field="id" enabled-field="enable"
:bglogo="getPlatformIcon(platform.type || platform.id)" @toggle-enabled="platformStatusChange"
@delete="deletePlatform" @edit="editPlatform">
<template #item-details="{ item }">
<!-- 平台运行状态 - 只在非运行状态或有错误时显示 -->
<div class="platform-status-row mb-2" v-if="getPlatformStat(item.id) && (getPlatformStat(item.id)?.status !== 'running' || getPlatformStat(item.id)?.error_count > 0)">
<!-- 状态 chip - 只在非 running 状态时显示 -->
<v-chip
v-if="getPlatformStat(item.id)?.status !== 'running'"
size="small"
:color="getStatusColor(getPlatformStat(item.id)?.status)"
variant="tonal"
class="status-chip"
>
<v-icon size="small" start>{{ getStatusIcon(getPlatformStat(item.id)?.status) }}</v-icon>
{{ tm('runtimeStatus.' + (getPlatformStat(item.id)?.status || 'unknown')) }}
</v-chip>
<!-- 错误数量提示 -->
<v-chip
v-if="getPlatformStat(item.id)?.error_count > 0"
size="small"
color="error"
variant="tonal"
class="error-chip"
:class="{ 'ms-2': getPlatformStat(item.id)?.status !== 'running' }"
@click.stop="showErrorDetails(item)"
>
<v-icon size="small" start>mdi-bug</v-icon>
{{ getPlatformStat(item.id)?.error_count }} {{ tm('runtimeStatus.errors') }}
</v-chip>
</div>
<div v-if="getPlatformStat(item.id)?.unified_webhook && item.webhook_uuid" class="webhook-info">
<v-chip
size="small"
color="primary"
variant="tonal"
class="webhook-chip"
@click.stop="openWebhookDialog(item.webhook_uuid)"
>
<v-icon size="small" start>mdi-webhook</v-icon>
{{ tm('viewWebhook') }}
</v-chip>
</div>
</template>
</item-card>
</v-col>
</v-row>
<div v-else class="item-grid">
<item-card v-for="(platform, index) in config_data.platform || []" :key="index"
:item="platform" title-field="id" enabled-field="enable"
:bglogo="getPlatformIcon(platform.type || platform.id)" @toggle-enabled="platformStatusChange"
@delete="deletePlatform" @edit="editPlatform">
<template #item-details="{ item }">
<div class="d-flex flex-row align-center flex-wrap gap-2 mt-1">
<!-- 平台运行状态 -->
<v-chip
v-if="getPlatformStat(item.id)?.status !== 'running' && getPlatformStat(item.id)?.status !== undefined"
size="x-small"
:color="getStatusColor(getPlatformStat(item.id)?.status)"
variant="tonal"
class="status-chip px-2"
>
<v-icon size="14" start>{{ getStatusIcon(getPlatformStat(item.id)?.status) }}</v-icon>
<span class="text-caption" style="font-size: 10px !important;">{{ tm('runtimeStatus.' + (getPlatformStat(item.id)?.status || 'unknown')) }}</span>
</v-chip>
<!-- 错误数量提示 -->
<v-chip
v-if="getPlatformStat(item.id)?.error_count > 0"
size="x-small"
color="error"
variant="tonal"
class="error-chip px-2"
@click.stop="showErrorDetails(item)"
>
<v-icon size="14" start>mdi-bug</v-icon>
<span class="text-caption" style="font-size: 10px !important;">{{ getPlatformStat(item.id)?.error_count }} {{ tm('runtimeStatus.errors') }}</span>
</v-chip>
<!-- Webhook -->
<v-chip
v-if="getPlatformStat(item.id)?.unified_webhook && item.webhook_uuid"
size="x-small"
color="primary"
variant="tonal"
class="webhook-chip px-2"
@click.stop="openWebhookDialog(item.webhook_uuid)"
>
<v-icon size="14" start>mdi-webhook</v-icon>
<span class="text-caption" style="font-size: 10px !important;">{{ tm('viewWebhook') }}</span>
</v-chip>
</div>
</template>
</item-card>
</div>
</div>

<!-- 日志部分 -->
Expand Down Expand Up @@ -453,28 +450,24 @@ export default {
padding-bottom: 40px;
}

.webhook-info {
margin-top: 4px;
.item-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: 16px;
width: 100%;
}

.webhook-chip {
cursor: pointer;
.gap-2 {
gap: 8px;
}

.platform-status-row {
display: flex;
.status-chip, .error-chip, .webhook-chip {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
}

.status-chip {
font-size: 12px;
}

.error-chip {
.error-chip, .webhook-chip {
cursor: pointer;
font-size: 12px;
}

.error-details {
Expand Down