astrbbbb / dashboard /src /components /extension /MarketPluginCard.vue
qa1145's picture
Upload 1245 files
8ede856 verified
<script setup>
import { ref, computed } from "vue";
import { useModuleI18n } from "@/i18n/composables";
import PluginPlatformChip from "@/components/shared/PluginPlatformChip.vue";
const { tm } = useModuleI18n("features/extension");
const props = defineProps({
plugin: {
type: Object,
required: true,
},
defaultPluginIcon: {
type: String,
required: true,
},
showPluginFullName: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["install"]);
const normalizePlatformList = (platforms) => {
if (!Array.isArray(platforms)) return [];
return platforms.filter((item) => typeof item === "string");
};
const platformDisplayList = computed(() =>
normalizePlatformList(props.plugin?.support_platforms),
);
const handleInstall = (plugin) => {
emit("install", plugin);
};
</script>
<template>
<v-card
class="rounded-lg d-flex flex-column plugin-card"
elevation="0"
style="height: 13rem; position: relative"
>
<v-chip
v-if="plugin?.pinned"
color="warning"
size="x-small"
label
style="
position: absolute;
right: 8px;
top: 8px;
z-index: 10;
height: 20px;
font-weight: bold;
"
>
{{ tm("market.recommended") }}
</v-chip>
<v-card-text
style="
padding: 12px;
padding-bottom: 8px;
display: flex;
gap: 12px;
width: 100%;
flex: 1;
overflow: hidden;
"
>
<div style="flex-shrink: 0">
<img
:src="plugin?.logo || defaultPluginIcon"
:alt="plugin.name"
style="
height: 75px;
width: 75px;
border-radius: 8px;
object-fit: cover;
"
/>
</div>
<div
style="
flex: 1;
overflow: hidden;
display: flex;
flex-direction: column;
"
>
<div
class="font-weight-bold"
style="
margin-bottom: 4px;
line-height: 1.3;
font-size: 1.2rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
"
>
<span style="overflow: hidden; text-overflow: ellipsis">
{{
plugin.display_name?.length
? plugin.display_name
: showPluginFullName
? plugin.name
: plugin.trimmedName
}}
</span>
</div>
<div class="d-flex align-center" style="gap: 4px; margin-bottom: 6px">
<v-icon
icon="mdi-account"
size="x-small"
style="color: rgba(var(--v-theme-on-surface), 0.5)"
></v-icon>
<a
v-if="plugin?.social_link"
:href="plugin.social_link"
target="_blank"
@click.stop
class="text-subtitle-2 font-weight-medium"
style="
text-decoration: none;
color: rgb(var(--v-theme-primary));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
"
>
{{ plugin.author }}
</a>
<span
v-else
class="text-subtitle-2 font-weight-medium"
style="
color: rgb(var(--v-theme-primary));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
"
>
{{ plugin.author }}
</span>
<div
class="d-flex align-center text-subtitle-2 ml-2"
style="color: rgba(var(--v-theme-on-surface), 0.7)"
>
<v-icon
icon="mdi-source-branch"
size="x-small"
style="margin-right: 2px"
></v-icon>
<span>{{ plugin.version }}</span>
</div>
</div>
<div class="text-caption plugin-description">
{{ plugin.desc }}
</div>
<div
v-if="plugin.astrbot_version || platformDisplayList.length"
class="d-flex align-center flex-wrap"
style="gap: 4px; margin-top: 4px; margin-bottom: 4px"
>
<v-chip
v-if="plugin.astrbot_version"
size="x-small"
color="secondary"
variant="outlined"
style="height: 20px"
>
AstrBot: {{ plugin.astrbot_version }}
</v-chip>
<PluginPlatformChip
:platforms="plugin.support_platforms"
size="x-small"
:chip-style="{ height: '20px' }"
/>
</div>
<div class="d-flex align-center" style="gap: 8px; margin-top: auto">
<div
v-if="plugin.stars !== undefined"
class="d-flex align-center text-subtitle-2"
style="color: rgba(var(--v-theme-on-surface), 0.7)"
>
<v-icon
icon="mdi-star"
size="x-small"
style="margin-right: 2px"
></v-icon>
<span>{{ plugin.stars }}</span>
</div>
<div
v-if="plugin.updated_at"
class="d-flex align-center text-subtitle-2"
style="color: rgba(var(--v-theme-on-surface), 0.7)"
>
<v-icon
icon="mdi-clock-outline"
size="x-small"
style="margin-right: 2px"
></v-icon>
<span>{{ new Date(plugin.updated_at).toLocaleString() }}</span>
</div>
</div>
</div>
</v-card-text>
<v-card-actions
style="gap: 6px; padding: 8px 12px; padding-top: 0"
@click.stop
>
<v-chip
v-for="tag in plugin.tags?.slice(0, 2)"
:key="tag"
:color="tag === 'danger' ? 'error' : 'primary'"
label
size="x-small"
style="height: 20px"
>
{{ tag === "danger" ? tm("tags.danger") : tag }}
</v-chip>
<v-menu v-if="plugin.tags && plugin.tags.length > 2" open-on-hover offset-y>
<template v-slot:activator="{ props: menuProps }">
<v-chip
v-bind="menuProps"
color="grey"
label
size="x-small"
style="height: 20px; cursor: pointer"
>
+{{ plugin.tags.length - 2 }}
</v-chip>
</template>
<v-list density="compact">
<v-list-item v-for="tag in plugin.tags.slice(2)" :key="tag">
<v-chip :color="tag === 'danger' ? 'error' : 'primary'" label size="small">
{{ tag === "danger" ? tm("tags.danger") : tag }}
</v-chip>
</v-list-item>
</v-list>
</v-menu>
<v-spacer></v-spacer>
<v-btn
v-if="plugin?.repo"
color="secondary"
size="small"
variant="tonal"
class="market-action-btn"
:href="plugin.repo"
target="_blank"
style="height: 32px"
>
<v-icon icon="mdi-github" start size="small"></v-icon>
{{ tm("buttons.viewRepo") }}
</v-btn>
<v-btn
v-if="!plugin?.installed"
color="primary"
size="small"
@click="handleInstall(plugin)"
variant="flat"
class="market-action-btn"
style="height: 32px"
>
{{ tm("buttons.install") }}
</v-btn>
<v-chip v-else color="success" size="x-small" label style="height: 20px">
{{ tm("status.installed") }}
</v-chip>
</v-card-actions>
</v-card>
</template>
<style scoped>
.plugin-description {
color: rgba(var(--v-theme-on-surface), 0.6);
line-height: 1.3;
margin-bottom: 6px;
flex: 1;
overflow-y: hidden;
}
.plugin-card:hover .plugin-description {
overflow-y: auto;
}
.plugin-description::-webkit-scrollbar {
width: 8px;
height: 8px;
}
.plugin-description::-webkit-scrollbar-track {
background: transparent;
}
.plugin-description::-webkit-scrollbar-thumb {
background-color: rgba(var(--v-theme-primary-rgb), 0.4);
border-radius: 4px;
border: 2px solid transparent;
background-clip: content-box;
}
.plugin-description::-webkit-scrollbar-thumb:hover {
background-color: rgba(var(--v-theme-primary-rgb), 0.6);
}
.market-action-btn {
font-size: 0.9rem;
font-weight: 600;
}
</style>