mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-27 19:29:52 +08:00
Refactor page titles and dialog close buttons across multiple views
- Replaced instances of `DialogCloseBtn` with `VDialogCloseBtn` in TransferHistoryView, AccountSettingAbout, AccountSettingSystem, and UserProfileView for consistency. - Introduced a new component `PageContentTitle` to standardize page titles in SiteCardListView, SubscribeListView, UserListView, and WorkflowListView, improving layout and readability. - Added keyword filtering functionality in SubscribeListView and SubscribeShareView to enhance user experience during searches. - Added a new site logo image to the assets.
This commit is contained in:
@@ -47,6 +47,9 @@ const loading = ref(false)
|
||||
// 已安装插件列表
|
||||
const dataList = ref<Plugin[]>([])
|
||||
|
||||
// 过滤后的已安装插件列表
|
||||
const filteredDataList = ref<Plugin[]>([])
|
||||
|
||||
// 未安装插件列表
|
||||
const uninstalledList = ref<Plugin[]>([])
|
||||
|
||||
@@ -101,6 +104,25 @@ const filterForm = reactive({
|
||||
repo: [] as string[],
|
||||
})
|
||||
|
||||
// 计算过滤表单是否全部为空
|
||||
const isFilterFormEmpty = computed(() => {
|
||||
return (
|
||||
filterForm.name === '' &&
|
||||
filterForm.author.length === 0 &&
|
||||
filterForm.label.length === 0 &&
|
||||
filterForm.repo.length === 0
|
||||
)
|
||||
})
|
||||
|
||||
// 插件过滤条件
|
||||
const installedFilter = ref('')
|
||||
|
||||
// 已安装插件过滤窗口
|
||||
const filterInstalledPluginDialog = ref(false)
|
||||
|
||||
// 插件市场过滤窗口
|
||||
const filterMarketPluginDialog = ref(false)
|
||||
|
||||
// 作者过滤项
|
||||
const authorFilterOptions = ref<string[]>([])
|
||||
// 标签过滤项
|
||||
@@ -380,6 +402,13 @@ function handleRepoUrl(url: string | undefined) {
|
||||
return url.replace('https://github.com/', '').replace('https://raw.githubusercontent.com/', '')
|
||||
}
|
||||
|
||||
// 监测dataList变化或installedFilter变化时更新filteredDataList
|
||||
watch([dataList, installedFilter], () => {
|
||||
filteredDataList.value = dataList.value.filter(item => {
|
||||
return item.plugin_name?.toLowerCase().includes(installedFilter.value.toLowerCase())
|
||||
})
|
||||
})
|
||||
|
||||
// 加载时获取数据
|
||||
onMounted(async () => {
|
||||
await loadPluginOrderConfig()
|
||||
@@ -399,10 +428,112 @@ onMounted(async () => {
|
||||
<div>
|
||||
<VHeaderTab :items="PluginTabs" v-model="activeTab">
|
||||
<template #append>
|
||||
<VMenu
|
||||
v-if="activeTab === '我的插件'"
|
||||
v-model="filterInstalledPluginDialog"
|
||||
width="20rem"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
<VBtn
|
||||
icon="mdi-filter-cog-outline"
|
||||
variant="text"
|
||||
:color="installedFilter ? 'primary' : 'gray'"
|
||||
size="default"
|
||||
class="settings-icon-button"
|
||||
v-bind="props"
|
||||
/>
|
||||
</template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-filter-cog-outline" class="mr-2" />
|
||||
筛选插件
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="filterInstalledPluginDialog = false" />
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<VTextField v-model="installedFilter" label="名称" density="comfortable" clearable />
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VMenu>
|
||||
<VMenu
|
||||
v-if="activeTab === '插件市场'"
|
||||
v-model="filterMarketPluginDialog"
|
||||
width="25rem"
|
||||
:close-on-content-click="false"
|
||||
>
|
||||
<template #activator="{ props }">
|
||||
<VBtn
|
||||
icon="mdi-filter-cog-outline"
|
||||
variant="text"
|
||||
:color="isFilterFormEmpty ? 'gray' : 'primary'"
|
||||
size="default"
|
||||
class="settings-icon-button"
|
||||
v-bind="props"
|
||||
/>
|
||||
</template>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<VCardTitle>
|
||||
<VIcon icon="mdi-filter-cog-outline" class="mr-2" />
|
||||
筛选插件
|
||||
</VCardTitle>
|
||||
<VDialogCloseBtn @click="filterMarketPluginDialog = false" />
|
||||
</VCardItem>
|
||||
<VCardText>
|
||||
<!-- 过滤表单 -->
|
||||
<div v-if="isAppMarketLoaded">
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="filterForm.name" density="comfortable" label="名称" clearable />
|
||||
</VCol>
|
||||
<VCol v-if="authorFilterOptions.length > 0" cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="filterForm.author"
|
||||
:items="authorFilterOptions"
|
||||
density="comfortable"
|
||||
chips
|
||||
label="作者"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="labelFilterOptions.length > 0" cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="filterForm.label"
|
||||
:items="labelFilterOptions"
|
||||
density="comfortable"
|
||||
chips
|
||||
label="标签"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="repoFilterOptions.length > 0" cols="12" md="6">
|
||||
<VSelect
|
||||
v-model="filterForm.repo"
|
||||
:items="repoFilterOptions"
|
||||
density="comfortable"
|
||||
chips
|
||||
label="插件库"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="repoFilterOptions.length > 0" cols="12" md="6">
|
||||
<VSelect v-model="activeSort" :items="sortOptions" density="comfortable" label="排序" />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
</VCardText>
|
||||
</VCard>
|
||||
</VMenu>
|
||||
<VBtn
|
||||
v-if="activeTab === '插件市场'"
|
||||
icon="mdi-store-cog"
|
||||
variant="text"
|
||||
color="primary"
|
||||
color="gray"
|
||||
size="default"
|
||||
class="settings-icon-button"
|
||||
@click="MarketSettingDialog = true"
|
||||
@@ -415,10 +546,11 @@ onMounted(async () => {
|
||||
<VWindowItem value="我的插件">
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<VPageContentTitle v-if="installedFilter" :title="`筛选:${installedFilter}`" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<draggable
|
||||
v-if="dataList.length > 0"
|
||||
v-model="dataList"
|
||||
v-if="filteredDataList.length > 0"
|
||||
v-model="filteredDataList"
|
||||
@end="savePluginOrder"
|
||||
handle=".cursor-move"
|
||||
item-key="id"
|
||||
@@ -437,10 +569,12 @@ onMounted(async () => {
|
||||
</template>
|
||||
</draggable>
|
||||
<NoDataFound
|
||||
v-if="dataList.length === 0 && isRefreshed"
|
||||
v-if="filteredDataList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有安装插件"
|
||||
error-description="点击右下角按钮,前往插件市场安装插件。"
|
||||
error-title="没有数据"
|
||||
:error-description="
|
||||
installedFilter ? '没有搜索到相关内容,请更换搜索关键词。' : '请先前往插件市场安装插件。'
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
@@ -450,50 +584,6 @@ onMounted(async () => {
|
||||
<transition name="fade-slide" appear>
|
||||
<div>
|
||||
<LoadingBanner v-if="!isAppMarketLoaded" class="mt-12" />
|
||||
<!-- 过滤表单 -->
|
||||
<div v-if="isAppMarketLoaded" class="bg-transparent mb-3 shadow-none">
|
||||
<VRow>
|
||||
<VCol cols="6" md="">
|
||||
<VTextField v-model="filterForm.name" density="compact" label="名称" clearable />
|
||||
</VCol>
|
||||
<VCol v-if="authorFilterOptions.length > 0" cols="6" md="">
|
||||
<VSelect
|
||||
v-model="filterForm.author"
|
||||
:items="authorFilterOptions"
|
||||
density="compact"
|
||||
chips
|
||||
label="作者"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="labelFilterOptions.length > 0" cols="6" md="">
|
||||
<VSelect
|
||||
v-model="filterForm.label"
|
||||
:items="labelFilterOptions"
|
||||
density="compact"
|
||||
chips
|
||||
label="标签"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="repoFilterOptions.length > 0" cols="6" md="">
|
||||
<VSelect
|
||||
v-model="filterForm.repo"
|
||||
:items="repoFilterOptions"
|
||||
density="compact"
|
||||
chips
|
||||
label="插件库"
|
||||
multiple
|
||||
clearable
|
||||
/>
|
||||
</VCol>
|
||||
<VCol v-if="repoFilterOptions.length > 0" cols="6" md="">
|
||||
<VSelect v-model="activeSort" :items="sortOptions" density="compact" label="排序" />
|
||||
</VCol>
|
||||
</VRow>
|
||||
</div>
|
||||
<div v-if="isAppMarketLoaded" class="grid gap-4 grid-plugin-card">
|
||||
<template v-for="(data, index) in sortedUninstalledList" :key="`${data.id}_v${data.plugin_version}`">
|
||||
<PluginAppCard :plugin="data" :count="PluginStatistics[data.id || '0']" @install="pluginInstalled" />
|
||||
@@ -555,7 +645,7 @@ onMounted(async () => {
|
||||
class="mx-1"
|
||||
/>
|
||||
</VToolbar>
|
||||
<DialogCloseBtn @click="closeSearchDialog" />
|
||||
<VDialogCloseBtn @click="closeSearchDialog" />
|
||||
<VList v-if="filterPlugins.length > 0" lines="three">
|
||||
<VVirtualScroll :items="filterPlugins">
|
||||
<template #default="{ item }">
|
||||
|
||||
@@ -690,7 +690,7 @@ onMounted(fetchData)
|
||||
<!-- 底部弹窗 -->
|
||||
<VBottomSheet v-model="deleteConfirmDialog" inset>
|
||||
<VCard class="text-center rounded-t">
|
||||
<DialogCloseBtn @click="deleteConfirmDialog = false" />
|
||||
<VDialogCloseBtn @click="deleteConfirmDialog = false" />
|
||||
<VCardTitle class="pe-10">
|
||||
{{ confirmTitle }}
|
||||
</VCardTitle>
|
||||
|
||||
@@ -258,7 +258,7 @@ onMounted(() => {
|
||||
<VDialog v-if="releaseDialog" v-model="releaseDialog" width="600" scrollable>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<DialogCloseBtn @click="releaseDialog = false" />
|
||||
<VDialogCloseBtn @click="releaseDialog = false" />
|
||||
<VCardTitle>{{ releaseDialogTitle }} 变更日志</VCardTitle>
|
||||
</VCardItem>
|
||||
<VCardText v-html="releaseDialogBody" />
|
||||
|
||||
@@ -554,7 +554,7 @@ onDeactivated(() => {
|
||||
<VDialog v-if="advancedDialog" v-model="advancedDialog" scrollable max-width="60rem" persistent>
|
||||
<VCard>
|
||||
<VCardItem>
|
||||
<DialogCloseBtn @click="advancedDialog = false" />
|
||||
<VDialogCloseBtn @click="advancedDialog = false" />
|
||||
<VCardTitle>高级设置</VCardTitle>
|
||||
<VCardSubtitle>系统进阶设置,特殊情况下才需要调整</VCardSubtitle>
|
||||
</VCardItem>
|
||||
|
||||
@@ -89,16 +89,7 @@ onActivated(() => {
|
||||
<template>
|
||||
<div class="card-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<div class="my-3 md:flex md:items-center md:justify-between">
|
||||
<div class="min-w-0 flex-1 mx-0 flex align-middle">
|
||||
<h2
|
||||
class="mb-3 ms-2 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-3xl sm:leading-9 md:mb-0"
|
||||
data-testid="page-header"
|
||||
>
|
||||
<span class="text-moviepilot">站点管理</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<VPageContentTitle title="站点管理" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<draggable
|
||||
v-if="siteList.length > 0"
|
||||
|
||||
@@ -19,11 +19,15 @@ const userStore = useUserStore()
|
||||
const props = defineProps({
|
||||
type: String,
|
||||
subid: String,
|
||||
keyword: String,
|
||||
})
|
||||
|
||||
// 是否刷新过
|
||||
let isRefreshed = ref(false)
|
||||
|
||||
// 搜索关键字
|
||||
const keyword = ref(props.keyword || '')
|
||||
|
||||
// 顺序存储键值
|
||||
const localOrderKey = props.type === '电影' ? 'MP_SUBSCRIBE_MOVIE_ORDER' : 'MP_SUBSCRIBE_TV_ORDER'
|
||||
const orderRequestKey = props.type === '电影' ? 'SubscribeMovieOrder' : 'SubscribeTvOrder'
|
||||
@@ -50,6 +54,10 @@ watch(dataList, () => {
|
||||
const userName = userStore.userName
|
||||
if (superUser) displayList.value = dataList.value.filter(data => data.type === props.type)
|
||||
else displayList.value = dataList.value.filter(data => data.type === props.type && data.username === userName)
|
||||
// 过滤关键字
|
||||
if (keyword.value) {
|
||||
displayList.value = displayList.value.filter(data => data.name.toLowerCase().includes(keyword.value.toLowerCase()))
|
||||
}
|
||||
// 排序
|
||||
sortSubscribeOrder()
|
||||
})
|
||||
@@ -139,6 +147,7 @@ onActivated(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPageContentTitle v-if="keyword" :title="`筛选:${keyword}`" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<draggable
|
||||
v-if="displayList.length > 0"
|
||||
@@ -156,8 +165,8 @@ onActivated(async () => {
|
||||
<NoDataFound
|
||||
v-if="displayList.length === 0 && isRefreshed"
|
||||
error-code="404"
|
||||
error-title="没有订阅"
|
||||
error-description="请通过搜索添加电影、电视剧订阅。"
|
||||
error-title="没有数据"
|
||||
:error-description="keyword ? '没有搜索到相关内容,请更换搜索关键词。' : '请通过搜索添加电影、电视剧订阅。'"
|
||||
/>
|
||||
<!-- 底部操作按钮 -->
|
||||
<div v-if="isRefreshed">
|
||||
|
||||
@@ -21,6 +21,9 @@ const apipath = 'subscribe/shares'
|
||||
// 当前页码
|
||||
const page = ref(1)
|
||||
|
||||
// 搜索关键字
|
||||
const keyword = ref(props.keyword)
|
||||
|
||||
// 是否加载中
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -36,7 +39,7 @@ function getParams() {
|
||||
let params = {
|
||||
page: page.value,
|
||||
count: 30,
|
||||
name: props.keyword,
|
||||
name: keyword.value,
|
||||
}
|
||||
return params
|
||||
}
|
||||
@@ -112,6 +115,7 @@ function removeData(id: number) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VPageContentTitle v-if="keyword" :title="`搜索:${keyword}`" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<VInfiniteScroll mode="intersect" side="end" :items="dataList" class="overflow-hidden" @load="fetchData">
|
||||
<template #loading />
|
||||
@@ -126,7 +130,7 @@ function removeData(id: number) {
|
||||
error-code="404"
|
||||
error-title="没有数据"
|
||||
:error-description="
|
||||
keyword ? '没有搜索到相关内容,请更换搜索关键词' : '未获取到分享订阅数据,未开启数据分享或服务器无法连接。'
|
||||
keyword ? '没有搜索到相关内容,请更换搜索关键词。' : '未获取到分享订阅数据,未开启数据分享或服务器无法连接。'
|
||||
"
|
||||
/>
|
||||
</VInfiniteScroll>
|
||||
|
||||
@@ -54,22 +54,11 @@ onActivated(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 页面标题 -->
|
||||
<VPageContentTitle title="用户管理" />
|
||||
<div class="card-list-container">
|
||||
<!-- 页面标题 -->
|
||||
<div class="my-3 md:flex md:items-center md:justify-between">
|
||||
<div class="min-w-0 flex-1 mx-0 flex align-middle">
|
||||
<h2
|
||||
class="mb-3 ms-2 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-3xl sm:leading-9 md:mb-0"
|
||||
data-testid="page-header"
|
||||
>
|
||||
<span class="text-moviepilot">用户管理</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载中提示 -->
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
|
||||
<!-- 用户卡片网格 -->
|
||||
<div v-if="allUsers.length > 0 && isRefreshed" class="grid gap- grid-user-card">
|
||||
<!-- 普通用户卡片 -->
|
||||
|
||||
@@ -57,7 +57,7 @@ const accountInfo = ref<User>({
|
||||
is_otp: false,
|
||||
permissions: {},
|
||||
settings: {},
|
||||
nickname: ''
|
||||
nickname: '',
|
||||
})
|
||||
|
||||
// 二维码信息
|
||||
@@ -135,15 +135,15 @@ async function saveAccountInfo() {
|
||||
}
|
||||
accountInfo.value.password = newPassword.value
|
||||
}
|
||||
|
||||
|
||||
// 将nickname保存到settings中,后端可以直接处理JSON对象
|
||||
if (accountInfo.value.nickname) {
|
||||
if (!accountInfo.value.settings) {
|
||||
accountInfo.value.settings = {};
|
||||
accountInfo.value.settings = {}
|
||||
}
|
||||
accountInfo.value.settings.nickname = accountInfo.value.nickname;
|
||||
accountInfo.value.settings.nickname = accountInfo.value.nickname
|
||||
}
|
||||
|
||||
|
||||
const oldUserName = accountInfo.value.name
|
||||
const oldAvatar = accountInfo.value.avatar
|
||||
accountInfo.value.avatar = currentAvatar.value
|
||||
@@ -151,10 +151,10 @@ async function saveAccountInfo() {
|
||||
isSaving.value = true
|
||||
try {
|
||||
// 创建一个临时对象来保存用户数据,确保所有字段都会发送
|
||||
const userData = { ...accountInfo.value };
|
||||
|
||||
const userData = { ...accountInfo.value }
|
||||
|
||||
const result: { [key: string]: any } = await api.put('user/', userData)
|
||||
|
||||
|
||||
if (result.success) {
|
||||
if (oldUserName !== currentUserName.value) {
|
||||
$toast.success(`【${oldUserName}】更名【${currentUserName.value}】,用户信息保存成功!`)
|
||||
@@ -312,12 +312,7 @@ watch(
|
||||
<VForm class="mt-6">
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="currentUserName"
|
||||
density="comfortable"
|
||||
readonly
|
||||
label="用户名"
|
||||
/>
|
||||
<VTextField v-model="currentUserName" density="comfortable" readonly label="用户名" />
|
||||
</VCol>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField v-model="accountInfo.email" density="comfortable" clearable label="邮箱" type="email" />
|
||||
@@ -430,7 +425,7 @@ watch(
|
||||
<VDialog v-if="otpDialog" v-model="otpDialog" max-width="45rem" scrollable>
|
||||
<!-- 开启双重验证弹窗内容 -->
|
||||
<VCard>
|
||||
<DialogCloseBtn @click="otpDialog = false" />
|
||||
<VDialogCloseBtn @click="otpDialog = false" />
|
||||
<VCardText>
|
||||
<h4 class="text-h4 text-center mb-6 mt-5">登录双重验证</h4>
|
||||
<h5 class="text-h5 font-weight-medium mb-2">身份验证器</h5>
|
||||
|
||||
@@ -46,17 +46,8 @@ onActivated(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 页面标题 -->
|
||||
<div class="my-3 md:flex md:items-center md:justify-between">
|
||||
<div class="min-w-0 flex-1 mx-0 flex align-middle">
|
||||
<h2
|
||||
class="mb-3 ms-2 truncate text-2xl font-bold leading-7 text-gray-100 sm:overflow-visible sm:text-3xl sm:leading-9 md:mb-0"
|
||||
data-testid="page-header"
|
||||
>
|
||||
<span class="text-moviepilot">工作流</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VPageContentTitle title="工作流" />
|
||||
<LoadingBanner v-if="!isRefreshed" class="mt-12" />
|
||||
<VRow v-if="workflowList.length > 0" class="match-height">
|
||||
<VCol cols="12" md="6" lg="4" v-for="item in workflowList" :key="item.id">
|
||||
|
||||
Reference in New Issue
Block a user