Files
AstrBot/dashboard/src/components/shared/ExtensionCard.vue
T

258 lines
8.5 KiB
Vue

<script setup lang="ts">
import { ref, computed, inject } from 'vue';
import { useCustomizerStore } from "@/stores/customizer";
import { useModuleI18n } from '@/i18n/composables';
import UninstallConfirmDialog from './UninstallConfirmDialog.vue';
const props = defineProps({
extension: {
type: Object,
required: true,
},
marketMode: {
type: Boolean,
default: false,
},
highlight: {
type: Boolean,
default: false,
},
});
// 定义要发送到父组件的事件
const emit = defineEmits([
'configure',
'update',
'reload',
'install',
'uninstall',
'toggle-activation',
'view-handlers',
'view-readme'
]);
const reveal = ref(false);
const showUninstallDialog = ref(false);
// 国际化
const { tm } = useModuleI18n('features/extension');
// 操作函数
const configure = () => {
emit('configure', props.extension);
};
const updateExtension = () => {
emit('update', props.extension);
};
const reloadExtension = () => {
emit('reload', props.extension);
};
const $confirm = inject("$confirm");
const installExtension = async () => {
emit('install', props.extension);
};
const uninstallExtension = async () => {
showUninstallDialog.value = true;
};
const handleUninstallConfirm = (options: { deleteConfig: boolean; deleteData: boolean }) => {
emit("uninstall", props.extension, options);
};
const toggleActivation = () => {
emit('toggle-activation', props.extension);
};
const viewHandlers = () => {
emit('view-handlers', props.extension);
};
const viewReadme = () => {
emit('view-readme', props.extension);
};
</script>
<template>
<v-card class="mx-auto d-flex flex-column" elevation="0" :style="{
position: 'relative',
backgroundColor: useCustomizerStore().uiTheme === 'PurpleTheme' ? marketMode ? '#f8f0dd' : '#ffffff' : '#282833',
color: useCustomizerStore().uiTheme === 'PurpleTheme' ? '#000000dd' : '#ffffff'
}">
<v-card-text style="padding: 16px; padding-bottom: 0px; display: flex; gap: 16px; width: 100%;">
<div v-if="extension?.logo">
<img :src="extension.logo" :alt="extension.name" cover width="100"/>
</div>
<div style="overflow-x: auto;">
<!-- Top-right three-dot menu -->
<div style="position: absolute; right: 8px; top: 8px; z-index: 5;">
<v-menu offset-y>
<template v-slot:activator="{ props: menuProps }">
<v-btn icon variant="text" aria-label="more" v-if="extension?.repo" :href="extension?.repo"
target="_blank">
<v-icon icon="mdi-github"></v-icon>
</v-btn>
<v-btn v-bind="menuProps" icon variant="text" aria-label="more">
<v-icon icon="mdi-dots-vertical"></v-icon>
</v-btn>
</template>
<v-list>
<v-list-item @click="viewReadme">
<v-list-item-title>📄 {{ tm('buttons.viewDocs') }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="marketMode && !extension?.installed" @click="installExtension">
<v-list-item-title>
{{ tm('buttons.install') }}</v-list-item-title>
</v-list-item>
<v-list-item v-if="marketMode && extension?.installed">
<v-list-item-title class="text--disabled">{{ tm('status.installed') }}</v-list-item-title>
</v-list-item>
<!-- Divider between market actions and plugin actions -->
<v-divider v-if="!marketMode" />
<template v-if="!marketMode">
<v-list-item @click="configure">
<v-list-item-title>
{{ tm('card.actions.pluginConfig') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="uninstallExtension">
<v-list-item-title class="text-error">{{ tm('card.actions.uninstallPlugin') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="reloadExtension">
<v-list-item-title>{{ tm('card.actions.reloadPlugin') }}</v-list-item-title>
</v-list-item>
<v-list-item @click="toggleActivation">
<v-list-item-title>
{{ extension.activated ? tm('buttons.disable') : tm('buttons.enable') }}{{
tm('card.actions.togglePlugin') }}
</v-list-item-title>
</v-list-item>
<v-list-item @click="viewHandlers">
<v-list-item-title>{{ tm('card.actions.viewHandlers') }} ({{ extension.handlers.length
}})</v-list-item-title>
</v-list-item>
<v-list-item @click="updateExtension">
<v-list-item-title>
{{ extension.has_update
? tm('card.actions.updateTo') + ' ' + extension.online_version
: tm('card.actions.reinstall') }}
</v-list-item-title>
</v-list-item>
</template>
</v-list>
</v-menu>
</div>
<div style="width: 100%; margin-bottom: 24px;">
<!-- 最多一行 -->
<div class="text-caption"
style="color: gray; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-right: 84px;">
{{ extension.author }} / {{ extension.name }}
</div>
<p class="text-h3 font-weight-black extension-title" :class="{ 'text-h4': $vuetify.display.xs }">
<span class="extension-title__text">{{ extension.display_name?.length ? extension.display_name : extension.name }}</span>
<v-tooltip location="top" v-if="extension?.has_update && !marketMode">
<template v-slot:activator="{ props: tooltipProps }">
<v-icon v-bind="tooltipProps" color="warning" class="ml-2" icon="mdi-update" size="small"></v-icon>
</template>
<span>{{ tm("card.status.hasUpdate") }}: {{ extension.online_version }}</span>
</v-tooltip>
<v-tooltip location="top" v-if="!extension.activated && !marketMode">
<template v-slot:activator="{ props: tooltipProps }">
<v-icon v-bind="tooltipProps" color="error" class="ml-2" icon="mdi-cancel" size="small"></v-icon>
</template>
<span>{{ tm("card.status.disabled") }}</span>
</v-tooltip>
</p>
<div class="mt-1 d-flex flex-wrap">
<v-chip color="primary" label size="small">
<v-icon icon="mdi-source-branch" start></v-icon>
{{ extension.version }}
</v-chip>
<v-chip v-if="extension?.has_update" color="warning" label size="small" class="ml-2">
<v-icon icon="mdi-arrow-up-bold" start></v-icon>
{{ extension.online_version }}
</v-chip>
<v-chip color="primary" label size="small" class="ml-2" v-if="extension.handlers?.length" @click="viewHandlers" style="cursor: pointer;">
<v-icon icon="mdi-cogs" start></v-icon>
{{ extension.handlers?.length }}{{ tm("card.status.handlersCount") }}
</v-chip>
<v-chip v-for="tag in extension.tags" :key="tag" :color="tag === 'danger' ? 'error' : 'primary'" label
size="small" class="ml-2">
{{ tag === 'danger' ? tm('tags.danger') : tag }}
</v-chip>
</div>
<div class="mt-2" :class="{ 'text-caption': $vuetify.display.xs }" style="overflow-y: auto; height: 70px; font-size: 90%;">
{{ extension.desc }}
</div>
</div>
</div>
</v-card-text>
<v-card-actions class="extension-actions">
<v-btn color="primary" size="small" @click="viewReadme">
{{ tm('buttons.viewDocs') }}
</v-btn>
<v-btn v-if="!marketMode" color="primary" size="small" @click="configure">
{{ tm('card.actions.pluginConfig') }}
</v-btn>
</v-card-actions>
</v-card>
<!-- 卸载确认对话框 -->
<UninstallConfirmDialog
v-model="showUninstallDialog"
@confirm="handleUninstallConfirm"
/>
</template>
<style scoped>
.extension-image-container {
display: flex;
align-items: center;
margin-left: 12px;
}
.extension-title {
display: flex;
align-items: center;
}
.extension-title__text {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-top: 6px
}
@media (max-width: 600px) {
.extension-image-container {
margin-left: 8px;
}
}
.extension-actions {
margin-top: auto;
gap: 8px;
}
</style>