feat:插件搜索

This commit is contained in:
jxxghp
2024-04-05 22:34:34 +08:00
parent 3a4e936938
commit a959594348
4 changed files with 217 additions and 11 deletions

View File

@@ -14,13 +14,14 @@ import store from '@/store'
// 输入参数
const props = defineProps({
plugin: Object as PropType<Plugin>,
count: Number,
count: Number, // 下载次数
action: Boolean, // 动作标识
width: String,
height: String,
})
// 定义触发的自定义事件
const emit = defineEmits(['remove', 'save'])
const emit = defineEmits(['remove', 'save', 'actionDone'])
// 背景颜色
const backgroundColor = ref('#28A9E1')
@@ -64,6 +65,14 @@ const isImageLoaded = ref(false)
// 图片是否加载失败
const imageLoadError = ref(false)
// 监听动作标识如为true则打开详情
watch(() => props.action, (newAction, oldAction) => {
if (newAction && !oldAction) {
openPluginDetail()
emit('actionDone')
}
})
// 图片加载完成
async function imageLoaded() {
isImageLoaded.value = true
@@ -286,6 +295,14 @@ function openLoggerWindow() {
window.open(url, '_blank')
}
// 打开插件详情
function openPluginDetail() {
if (props.plugin?.has_page)
showPluginInfo()
else
showPluginConfig()
}
// 弹出菜单
const dropdownItems = ref([
{
@@ -372,12 +389,7 @@ watch(() => props.plugin?.has_update, (newHasUpdate, oldHasUpdate) => {
v-if="isVisible"
:width="props.width"
:height="props.height"
@click="() => {
if (props.plugin?.has_page)
showPluginInfo()
else
showPluginConfig()
}"
@click="openPluginDetail"
>
<div
class="relative pa-4 text-center card-cover-blurred"

View File

@@ -131,7 +131,6 @@ onMounted(() => {
</VListItemTitle>
<VListItemSubtitle class="mt-2" v-html="item.overview" />
</VListItem>
<VDivider v-if="i < items.length - 1" class="mt-1" inset />
</template>
</VList>
</VCard>

View File

@@ -304,6 +304,7 @@ async function transfer() {
v-model="tmdbSelectorDialog"
width="40rem"
scrollable
max-height="90vh"
>
<TmdbSelectorCard
v-model="transferForm.tmdbid"

View File

@@ -1,9 +1,11 @@
<script lang="ts" setup>
import { useToast } from 'vue-toast-notification'
import api from '@/api'
import type { Plugin } from '@/api/types'
import NoDataFound from '@/components/NoDataFound.vue'
import PluginAppCard from '@/components/cards/PluginAppCard.vue'
import PluginCard from '@/components/cards/PluginCard.vue'
import noImage from '@images/logos/plugin.png'
// 已安装插件列表
const dataList = ref<Plugin[]>([])
@@ -23,16 +25,115 @@ const PluginAppDialog = ref(false)
// 插件安装统计
const PluginStatistics = ref<{ [key: string]: number }>({})
// 搜索窗口
const SearchDialog = ref(false)
// 搜索关键字
const keyword = ref('')
// 每一个插件的图标加载状态
const pluginIconLoaded = ref<{ [key: string]: boolean }>({})
// 每一个插件的动作标识
const pluginActions = ref<{ [key: string]: boolean }>({})
// 提示框
const $toast = useToast()
// 进度框
const progressDialog = ref(false)
// 进度框文本
const progressText = ref('正在安装插件...')
// 关闭插件市场窗口
function pluginDialogClose() {
PluginAppDialog.value = false
}
// 安装插件
async function installPlugin(item: Plugin) {
try {
// 显示等待提示框
progressDialog.value = true
progressText.value = `正在安装 ${item?.plugin_name} v${item?.plugin_version} ...`
const result: { [key: string]: any } = await api.get(
`plugin/install/${item?.id}`,
{
params: {
repo_url: item?.repo_url,
force: item?.has_update,
},
},
)
// 隐藏等待提示框
progressDialog.value = false
if (result.success) {
$toast.success(`插件 ${item?.plugin_name} 安装成功!`)
// 刷新
refreshData()
}
else {
$toast.error(`插件 ${item?.plugin_name} 安装失败:${result.message}`)
}
}
catch (error) {
console.error(error)
}
}
// 打开插件搜索结果
function openPlugin(item: Plugin) {
// 如果是已安装插件则打开插件详情
if (item.installed === true) {
// 标记插件动作
pluginActions.value[item.id || '0'] = true
}
else {
// 如果是未安装插件则安装
installPlugin(item)
}
closeSearchDialog()
}
// 关闭插件搜索窗口
function closeSearchDialog() {
SearchDialog.value = false
}
// 插件图标加载错误
function pluginIconError(item: Plugin) {
pluginIconLoaded.value[item.id || '0'] = false
}
// 插件图标地址
function pluginIcon(item: Plugin) {
// 如果图片加载错误
if (pluginIconLoaded.value[item.id || '0'] === false)
return noImage
// 如果是网络图片则使用代理后返回
if (item?.plugin_icon?.startsWith('http'))
return `${import.meta.env.VITE_API_BASE_URL}system/img/${encodeURIComponent(item?.plugin_icon)}/1`
return `./plugin_icon/${item?.plugin_icon}`
}
// 过滤插件
const filterPlugins = computed(() => {
const all_list = [...dataList.value, ...uninstalledList.value]
return all_list.filter((item: Plugin) => {
return item.plugin_name?.includes(keyword.value) || item.plugin_desc?.includes(keyword.value)
})
})
// 新安装了插件
function pluginInstalled() {
fetchInstalledPlugins()
pluginDialogClose()
fetchUninstalledPlugins()
refreshData()
}
// 获取插件列表数据
@@ -129,8 +230,10 @@ onBeforeMount(() => {
:key="`${data.id}_v${data.plugin_version}`"
:count="PluginStatistics[data.id || '0']"
:plugin="data"
:action="pluginActions[data.id || '0']"
@remove="refreshData"
@save="refreshData"
@action-done="pluginActions[data.id || '0'] = false"
/>
</div>
<NoDataFound
@@ -213,6 +316,97 @@ onBeforeMount(() => {
</VCardText>
</VCard>
</VDialog>
<!-- 插件搜索 -->
<VDialog
v-model="SearchDialog"
scrollable
:z-index="1010"
max-width="40rem"
max-height="90vh"
>
<!-- Dialog Activator -->
<template #activator="{ props }">
<VFab
v-bind="props"
icon="mdi-magnify"
color="info"
location="bottom end"
class="mb-2"
size="x-large"
fixed
app
appear
/>
</template>
<VCard
class="mx-auto"
width="100%"
>
<VToolbar flat class="p-0">
<VTextField
v-model="keyword"
label="搜索插件"
single-line
placeholder="插件名称或描述"
variant="solo"
append-inner-icon="mdi-magnify"
flat
class="mx-1"
/>
</VToolbar>
<VList
v-if="filterPlugins.length > 0"
lines="two"
>
<template v-for="(item, i) in filterPlugins" :key="i">
<VListItem
@click="openPlugin(item)"
>
<template #prepend>
<VAvatar>
<VImg
:src="pluginIcon(item)"
@error="pluginIconError(item)"
>
<template #placeholder>
<div class="w-full h-full">
<VSkeletonLoader class="object-cover aspect-w-1 aspect-h-1" />
</div>
</template>
</VImg>
</VAvatar>
</template>
<VListItemTitle>
{{ item.plugin_name }}<span class="text-sm ms-2 mt-1 text-gray-500">v{{ item?.plugin_version }}</span>
<ExistIcon v-if="item.installed" />
</VListItemTitle>
<VListItemSubtitle class="mt-2" v-html="item.plugin_desc" />
</VListItem>
</template>
</VList>
</VCard>
</VDialog>
<!-- 安装插件进度框 -->
<VDialog
v-model="progressDialog"
:scrim="false"
width="25rem"
>
<VCard
color="primary"
>
<VCardText class="text-center">
{{ progressText }}
<VProgressLinear
indeterminate
color="white"
class="mb-0 mt-1"
/>
</VCardText>
</VCard>
</VDialog>
</template>
<style lang="scss">